<?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: David Ingraham</title>
    <description>The latest articles on DEV Community by David Ingraham (@cydavid).</description>
    <link>https://dev.to/cydavid</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%2F1641776%2F2ac1fcc1-59ae-4b42-a6f0-4f6de5d7e4cd.jpeg</url>
      <title>DEV Community: David Ingraham</title>
      <link>https://dev.to/cydavid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cydavid"/>
    <language>en</language>
    <item>
      <title>Treating the UI as a Contract: Eliminating the Wait in Modern Development</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Mon, 20 Apr 2026 01:07:20 +0000</pubDate>
      <link>https://dev.to/cydavid/treating-the-ui-as-a-contract-eliminating-the-wait-in-modern-development-673</link>
      <guid>https://dev.to/cydavid/treating-the-ui-as-a-contract-eliminating-the-wait-in-modern-development-673</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This blog and concept was a collaboration between &lt;a href="https://www.linkedin.com/in/leonardolanni/" rel="noopener noreferrer"&gt;Leonardo Lanni&lt;/a&gt; &amp;amp; &lt;a href="https://www.linkedin.com/in/dingraham01/" rel="noopener noreferrer"&gt;David Ingraham&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Most teams don’t build features.&lt;br&gt;
&lt;strong&gt;They wait on them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Development waits for product.&lt;br&gt;
Then front-end waits for back-end.&lt;br&gt;
Meanwhile QA waits for front-end and back-end.&lt;br&gt;
And finally, testing gets squeezed at the end.&lt;/p&gt;

&lt;p&gt;By the time everything connects, bugs show up when context is already gone. Does this sound familiar?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you never had to wait in the first place?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This blog introduces a different approach: treating the UI as a contract so everyone can move in parallel from day one. This means instead of work flowing in sequence, everything moves at once. Instead of testing being squeezed at the end, it starts immediately. And instead of integration being discovery, it becomes validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites for Success
&lt;/h2&gt;

&lt;p&gt;This approach is powerful, but it’s not something you can drop into any team overnight. If your team struggles with communication, this will expose it. If ownership is unclear, this will amplify it. If your process depends on clean handoffs, this will break that expectation entirely.&lt;/p&gt;

&lt;p&gt;Because this model replaces handoffs with collaboration.&lt;/p&gt;

&lt;p&gt;For it to work, a few things need to be true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strong cross-functional collaboration&lt;/strong&gt;&lt;br&gt;
Front-end, back-end, QA, and product stay aligned continuously, not sequentially.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A shift-left mindset&lt;/strong&gt;&lt;br&gt;
Teams define behavior, write tests, and validate flows before everything is fully implemented.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared ownership of quality&lt;/strong&gt;&lt;br&gt;
Testing isn’t owned by QA alone. Everyone contributes early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comfort with partial implementations&lt;/strong&gt;&lt;br&gt;
Mocked data, incomplete UIs, and evolving designs are part of the process, not blockers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these, things can get messy fast.&lt;/p&gt;

&lt;p&gt;But with the right culture in place, this becomes a force multiplier for both speed and quality. With this in mind, let’s move on to how this is implemented in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Idea
&lt;/h2&gt;

&lt;p&gt;As soon as a screen exists, it becomes a contract.&lt;/p&gt;

&lt;p&gt;A feature is typically composed of multiple screens. The moment one is defined, we establish how it should behave. This is the UI contract.&lt;/p&gt;

&lt;p&gt;This contract can take different forms depending on the team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;design-driven contract&lt;/strong&gt; (Figma designs, product requirements, defined behaviors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A code-driven contract&lt;/strong&gt; (a UI skeleton with mocked data and interactions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both serve the same purpose: defining how the UI should behave before full implementation. Finding the right fit for your team is crucial in gaining buy-in.&lt;/p&gt;

&lt;p&gt;In practice, we move quickly from design into a &lt;strong&gt;UI skeleton&lt;/strong&gt; that becomes the executable version of that contract.&lt;/p&gt;

&lt;p&gt;This isn’t a prototype or throwaway code. It’s the real UI, just powered by mocked data and behavior instead of live APIs.&lt;/p&gt;

&lt;p&gt;This skeleton defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All UI controls (inputs, buttons, tables, modals, etc.)&lt;/li&gt;
&lt;li&gt;Stable selectors (data-test ids, accessibility roles, labels)&lt;/li&gt;
&lt;li&gt;Navigation and validation rules&lt;/li&gt;
&lt;li&gt;Realistic mocked behavior instead of API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to structure and interactions, the UI contract also defines how the application behaves under different scenarios.&lt;/p&gt;

&lt;p&gt;And importantly, it defines how the system behaves under real-world conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Empty results&lt;/li&gt;
&lt;li&gt;Permission errors (e.g. 403 Forbidden)&lt;/li&gt;
&lt;li&gt;Validation failures (e.g. 422 with field-level errors)&lt;/li&gt;
&lt;li&gt;System errors&lt;/li&gt;
&lt;li&gt;Slow responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our implementation, these behaviors are formalized through structured error types, ensuring that both mocked and real providers expose consistent outcomes to the UI.&lt;/p&gt;

&lt;p&gt;Throughout development, the mocked behavior is replaced with real API integrations, but the structure, selectors, and expected behavior remain the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Once the contract exists, everything changes.&lt;br&gt;&lt;br&gt;
Instead of waiting, the team moves in parallel.&lt;/p&gt;

&lt;p&gt;This approach is built around three core pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI skeleton (the contract itself)&lt;/li&gt;
&lt;li&gt;A mocked behavior layer&lt;/li&gt;
&lt;li&gt;Parallel work-streams across the team&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  1. UI Skeleton (Contract)
&lt;/h3&gt;

&lt;p&gt;Early in development, we implement the UI structure itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page structure and routing&lt;/li&gt;
&lt;li&gt;Validation rules&lt;/li&gt;
&lt;li&gt;All system states (loading, empty, error, success)&lt;/li&gt;
&lt;li&gt;Basic layout and placeholders (UI controls, selectors, structure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, how this definition looks can change between teams. Whether it’s a Figma design, detailed product ticket, real code, or a combination of these, this becomes the shared interface across front-end, back-end, and QA.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Mocked Behavior Layer
&lt;/h3&gt;

&lt;p&gt;Rather than calling real services, the UI is powered by a mocked data layer (fixtures, MSW, dependency injection, etc.).&lt;/p&gt;

&lt;p&gt;But these aren’t random mocks.&lt;/p&gt;

&lt;p&gt;They simulate real-world scenarios like we alluded to earlier, such as successful or empty responses, validation errors, permission issues, or response latency.&lt;/p&gt;

&lt;p&gt;Rather than exposing raw HTTP responses, providers return structured outcomes like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;forbidden&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validation_error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system_error&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures the UI behaves consistently regardless of whether it is connected to mocked data or real back-end services.&lt;/p&gt;

&lt;p&gt;The key is that the &lt;strong&gt;UI behaves as if it were&lt;/strong&gt; connected to real systems.&lt;/p&gt;

&lt;p&gt;As development progresses, we ensure mocked logic is replaced with real API integrations and the contract always stays stable.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Parallel Work-streams
&lt;/h3&gt;

&lt;p&gt;With the contract in place, different roles can fully move independently without waiting on each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QA&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures proper selectors exist&lt;/li&gt;
&lt;li&gt;Starts writing UI/E2E tests immediately&lt;/li&gt;
&lt;li&gt;Run the same tests later against real APIs and functioning UI without rewriting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Front-end Developers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build real logic incrementally&lt;/li&gt;
&lt;li&gt;Swap mocks for real services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Back-end Developers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define and implement APIs&lt;/li&gt;
&lt;li&gt;Use the UI and tests as a guide for expected behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Designers (or Product)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refine visuals as feedback arises&lt;/li&gt;
&lt;li&gt;Establish visual baselines once designs stabilize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of handing work off between roles, everyone moves forward at the same time against a shared, stable interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI-Level TDD (Test-Driven Development)
&lt;/h2&gt;

&lt;p&gt;This approach enables a form of &lt;strong&gt;TDD at the UI leve&lt;/strong&gt;l.&lt;/p&gt;

&lt;p&gt;This is possible, again, by defining and validating UI behavior upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define UI behavior and states&lt;/li&gt;
&lt;li&gt;Implement UI skeleton with mocks&lt;/li&gt;
&lt;li&gt;Write automated UI tests&lt;/li&gt;
&lt;li&gt;Replace mocked logic with real implementations&lt;/li&gt;
&lt;li&gt;Tests remain unchanged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key benefit is that tests are written early and remain stable.&lt;/p&gt;

&lt;p&gt;They validate behavior, not implementation details, so they continue to pass as the system evolves.&lt;/p&gt;

&lt;p&gt;In practice, this is reinforced by running the same automated tests against both mocked and real implementations.&lt;/p&gt;

&lt;p&gt;This dual-mode execution acts as a continuous validation that the UI contract holds across different environments and implementations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Visual Regression Strategy
&lt;/h2&gt;

&lt;p&gt;Visual testing can also be introduced in phases to match the maturity of the UI.&lt;/p&gt;

&lt;p&gt;Early on, visual tests are optional or non-blocking and the focus is on behavior, not polish.&lt;/p&gt;

&lt;p&gt;Later though, designs are finalized and approved. Visual baselines are then established and visual regression tests become blocking.&lt;/p&gt;

&lt;p&gt;At that point, visuals become part of the contract too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;

&lt;p&gt;This all sounds good in theory.&lt;br&gt;&lt;br&gt;
But what does it actually look like in practice?&lt;/p&gt;

&lt;p&gt;Let’s pretend we need to build a new order form. Like most features, it starts with a detailed product ticket, business requirements, and a Figma design.&lt;/p&gt;

&lt;p&gt;From there, we refine the feature across QA, front-end, back-end, and product. The goal isn’t just to scope the work, but to align on how the UI should behave, what edge cases exist, and what “done” actually looks like. This step is crucial because it ensures the entire team not only understands the upcoming feature, but has the opportunity to contribute to its design. &lt;strong&gt;No misunderstandings.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the feature is defined, we treat the design and product definition as the contract and start work in parallel. Throughout development, &lt;strong&gt;everyone communicates&lt;/strong&gt;. Daily status updates and raising any additional questions or engineering concerns ensure no one drifts out of the loop.&lt;/p&gt;

&lt;p&gt;Eventually, by the time the back-end endpoints are ready, the UI is mostly complete and tests are ~90% done. The last bit of work is just pulling in everyone’s changes and making the small adjustments needed to hook the entire flow up.&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%2Fm7quhrxghk8bre5kiadf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7quhrxghk8bre5kiadf.webp" alt=" " width="512" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bugs are caught early, feedback is fast, and there are no last-minute scrambles. By the time integration began, everything was already aligned.&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes this different
&lt;/h2&gt;

&lt;p&gt;At first glance, this can feel familiar — API mocking, shift-left testing, even prototyping. And in some ways, it builds on all of them.&lt;/p&gt;

&lt;p&gt;But the difference is what everything is anchored to.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;API mocking&lt;/strong&gt;, teams simulate backend behavior to unblock development. But those mocks are often temporary and loosely defined, drifting from reality over time.&lt;/p&gt;

&lt;p&gt;Here, mocks are part of the contract itself. They don’t just return data, they define expected behaviors like validation errors, permission failures, and edge cases. Both mocked and real implementations are expected to behave the same way.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;shift-left testing&lt;/strong&gt;, tests are written earlier — but they’re still often reacting to evolving systems and unstable interfaces.&lt;/p&gt;

&lt;p&gt;In this approach, tests are written against a stable UI contract from the start. They don’t need to change as implementation evolves, because they validate behavior — not how that behavior is implemented.&lt;/p&gt;

&lt;p&gt;And while &lt;strong&gt;prototypes&lt;/strong&gt; are typically throwaway, the UI skeleton here is not. It starts early, supports real testing, and evolves directly into the final product. Nothing gets discarded.&lt;/p&gt;

&lt;p&gt;The shift is subtle but important:&lt;/p&gt;

&lt;p&gt;The UI isn’t just something we build and test.&lt;br&gt;&lt;br&gt;
It becomes &lt;strong&gt;the contract that everything else aligns to&lt;/strong&gt; and that contract is executable from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Demo Repository
&lt;/h2&gt;

&lt;p&gt;To make this concept tangible, we built a small demo repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/llanni/ui-contract-testing-demo" rel="noopener noreferrer"&gt;ui-contract-testing-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It showcases the approach end-to-end, starting with a UI skeleton that defines routes, controls, selectors, and states. This is paired with a provider abstraction that supports both mocked and real implementations, allowing the UI to behave consistently regardless of the data source.&lt;/p&gt;

&lt;p&gt;The demo also includes a range of realistic scenarios — from happy paths to empty states, validation errors, permission issues, and even slow responses so behavior can be validated under real-world conditions, not just ideal ones.&lt;/p&gt;

&lt;p&gt;On top of that, the same Playwright test suite &lt;strong&gt;runs against both mocked and real environments&lt;/strong&gt;. This means testing can start early, remain stable throughout development, and continuously validate integrations without needing to rewrite tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;By treating the UI as a contract, teams unlock a fundamentally different way of building software. Testing starts from day one, not the end. Build the contract first. Let everyone move against it. Replace mocks with reality over time.&lt;/p&gt;

&lt;p&gt;This isn’t about making testing faster. It’s about removing the conditions that made you wait in the first place.&lt;/p&gt;

&lt;p&gt;No bottlenecks.&lt;br&gt;
No last-minute scrambles.&lt;br&gt;
No rebuilding context when it matters most. Just continuous progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eventually, you realize you were never blocked… you were just waiting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From both of us, happy testing,&lt;br&gt;
&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/leonardolanni/" rel="noopener noreferrer"&gt;Leonardo Lanni&lt;/a&gt; &amp;amp; &lt;a href="https://www.linkedin.com/in/dingraham01/" rel="noopener noreferrer"&gt;David Ingraham&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>ui</category>
    </item>
    <item>
      <title>Your Cypress Tests Are Slower Than You Think</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Mon, 20 Apr 2026 00:03:45 +0000</pubDate>
      <link>https://dev.to/cydavid/your-cypress-tests-are-slower-than-you-think-8ba</link>
      <guid>https://dev.to/cydavid/your-cypress-tests-are-slower-than-you-think-8ba</guid>
      <description>&lt;p&gt;If you’ve worked on a decent-sized Cypress suite, you’ve lived this.&lt;/p&gt;

&lt;p&gt;You push code. Pipeline kicks off.&lt;br&gt;
Then you wait.&lt;br&gt;
And you wait.&lt;br&gt;
…Still waiting.&lt;/p&gt;

&lt;p&gt;You tab over to Slack. Maybe check a PR. Start something else.&lt;br&gt;
Eventually results come back. Green, red, who knows.&lt;/p&gt;

&lt;p&gt;But here’s the problem: by the time you see them… you don’t care anymore. That’s the real issue with slow tests. Not time.&lt;/p&gt;

&lt;p&gt;It’s that they quietly disconnect themselves from how engineers actually work. And once that happens, the tests don’t just get slower, they stop mattering.&lt;/p&gt;

&lt;p&gt;So yeah, we’ll talk about how to speed things up.&lt;br&gt;
But this is really about something else: why slow tests break the feedback loop, and how to fix it before your suite turns into expensive decoration.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Hidden Cost of Slow Tests
&lt;/h2&gt;

&lt;p&gt;Everyone talks about wait time. That’s the obvious part.&lt;/p&gt;

&lt;p&gt;The real cost is behavioral.&lt;br&gt;
Fast tests keep developers in the loop:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;write code → run tests → fix problem → move on&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It’s tight. Feedback is immediate. You stay in context.&lt;br&gt;
Slow tests break that loop:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;write code → push commit → switch task → failure appears later&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you’re doing archaeology. And unless you secretly enjoy digging through commits like it’s a crime scene, it’s not a great place to be.&lt;/p&gt;

&lt;p&gt;Multiply that across a team and something subtle happens.&lt;br&gt;
People stop waiting.&lt;/p&gt;

&lt;p&gt;Tests still run. Pipelines still pass or fail.&lt;br&gt;
But they stop influencing decisions.&lt;/p&gt;

&lt;p&gt;That’s the line most teams don’t notice crossing.&lt;/p&gt;

&lt;p&gt;And if you’ve been around long enough, you know where that ends.&lt;/p&gt;

&lt;p&gt;First they’re flaky.&lt;br&gt;
Then they’re ignored.&lt;br&gt;
Eventually, someone suggests deleting or skipping them “just for now.”&lt;/p&gt;

&lt;p&gt;And somehow, they never come back.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 1-Second Problem
&lt;/h2&gt;

&lt;p&gt;Most slow test suites don’t have a single obvious culprit, they bleed out from a hundred small paper cuts.&lt;/p&gt;

&lt;p&gt;A second here.&lt;br&gt;
Two seconds there.&lt;br&gt;
A login repeated 100 times.&lt;br&gt;
A page load you didn’t question.&lt;br&gt;
One hard-coded wait that turns into ten next month.&lt;/p&gt;

&lt;p&gt;Each one feels harmless. That’s the problem though. Tests don’t run once — they run at scale. And when you scale up a single second, it adds up fast.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;1 second × 300 tests = 5 minutes&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Five minutes, from something no one even noticed or questioned. Now imagine what happens when you have ten of these 1-second problems. Now 100. That’s how teams end up with 30-minute pipelines without ever making a “bad” decision. It’s not one accidental mistake.&lt;/p&gt;

&lt;p&gt;It’s long-term, unnoticed accumulation.&lt;/p&gt;


&lt;h2&gt;
  
  
  Test Optimizations
&lt;/h2&gt;

&lt;p&gt;Now that we’ve defined why this matters, let’s actually fix it. We’ll be breaking up the solutions into three core sections, Test, Pipeline and Startup Optimizations. First, let’s start where the damage usually begins, inside the tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replace Hard-Coded Waits
&lt;/h3&gt;

&lt;p&gt;Hard waits are one of the easiest ways to waste time and one of the easiest problems to spot.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This ensures that your test is sitting there for 5 seconds, regardless if the app actually responded in 200ms. Every time.&lt;/p&gt;

&lt;p&gt;Instead, wait for real signals. Use dynamic-waits instead, such as waiting on real endpoint using &lt;a href="http://docs.cypress.io/api/commands/intercept" rel="noopener noreferrer"&gt;cy.intercept&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define an intercept on a real API&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Wait on the real load before interacting with the UI&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@orders&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;order-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also add “safe-guard” assertions on the UI too, utilizing Cypress’s &lt;a href="https://docs.cypress.io/app/core-concepts/retry-ability#Commands-Queries-and-Assertions" rel="noopener noreferrer"&gt;built-in retry system&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;// Wait for the button to exist in the DOM and be in a clickable state&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&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;The key idea is simple: don’t wait for time, wait for state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Login Sessions
&lt;/h3&gt;

&lt;p&gt;Login flows are also silent killers and a great example of the 1-second problem. They don’t look expensive but run across hundreds of tests and suddenly you’ve built a login simulator, not a test suite.&lt;/p&gt;

&lt;p&gt;This is especially common in suites where every test starts the same 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="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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=email]&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;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=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;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=login]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this looks harmless until you realize you’re doing it 200+ times per run. Instead, use &lt;a href="https://docs.cypress.io/api/commands/session" rel="noopener noreferrer"&gt;cy.session&lt;/a&gt;. Cypress gives you a built-in way to cache authentication and reuse it across tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&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="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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=email]&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;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=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;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=login]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now instead of logging in every time, Cypress will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run this once&lt;/li&gt;
&lt;li&gt;cache the session&lt;/li&gt;
&lt;li&gt;restore it for the rest of your tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No extra UI steps. No repeated logins. No wasted time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Redundant Checks
&lt;/h3&gt;

&lt;p&gt;To be clear, assertions aren’t the problem. End-to-end tests live or die by the assertions they make. The issue is repeated work.&lt;/p&gt;

&lt;p&gt;Cypress actually encourages &lt;a href="https://docs.cypress.io/app/core-concepts/best-practices?utm_source=chatgpt.com#Creating-Tiny-Tests-With-A-Single-Assertion" rel="noopener noreferrer"&gt;multiple assertions&lt;/a&gt; when they help prove meaningful behavior. The problem shows up when tests keep re-querying the DOM or stacking checks that don’t add much confidence. Every extra &lt;code&gt;cy.get()&lt;/code&gt; call isn’t free.&lt;/p&gt;

&lt;p&gt;Take a look at this example validating a simple success state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;.order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;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;.order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.order&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;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;Order created&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;.order-title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;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;.order-price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, this proves the order exists and contains the expected text but at some point, we’re not improving the test anymore. We’re just making Cypress do more laps, creating a tiny performance bloat.&lt;/p&gt;

&lt;p&gt;Most of the time, you can collapse this into something tighter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if the real goal is proving the user outcome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn’t fewer assertions for the sake of it. The goal is fewer redundant checks, fewer duplicate queries, and more signal per line of test code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mock Expensive Requests
&lt;/h3&gt;

&lt;p&gt;Your app does a lot of things your test does not care about.&lt;br&gt;
Images. Analytics. Recommendation engines. Background noise.&lt;/p&gt;

&lt;p&gt;And yet, your tests patiently wait for all of it, especially when running against a prod-like environment. This usually shows up as slow page loads, inconsistent timing, and tests that feel fine locally but drag in CI.&lt;/p&gt;

&lt;p&gt;Let’s say your page loads a directory which includes on load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 images&lt;/li&gt;
&lt;li&gt;3 analytics calls&lt;/li&gt;
&lt;li&gt;a recommendation engine hitting some external service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that matters if your test is just trying to verify:&lt;br&gt;
&lt;em&gt;“Can a user create an order?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But your test still pays the cost for it. Every time.&lt;/p&gt;

&lt;p&gt;Instead, intercept and stub these requests to skip the overhead entirely, using our trusty friend &lt;code&gt;cy.intercept&lt;/code&gt; again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/*&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;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeholder.png&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;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/analytics/*&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;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/recommendations/*&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;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;empty.json&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;Now our test is going to load faster with reduced network noise for a test that has no dependencies on these calls.&lt;/p&gt;

&lt;p&gt;That being said, mock responsibly. Don’t mock if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the request is part of the core user flow&lt;/li&gt;
&lt;li&gt;you’re validating real integration behavior&lt;/li&gt;
&lt;li&gt;the response directly impacts what the user sees or does&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your test is about creating an order, then test that, not whether your dashboard images finished loading first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pipeline Optimizations
&lt;/h2&gt;

&lt;p&gt;Even with clean, efficient tests, your pipeline can still be the bottleneck.&lt;/p&gt;

&lt;p&gt;At some point, the slowdown isn’t in your tests anymore. It’s in your infrastructure. Even if your tests are clean, your pipeline can still be the bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run Tests in Parallel
&lt;/h3&gt;

&lt;p&gt;This is usually the biggest win available and pretty straight-forward.&lt;/p&gt;

&lt;p&gt;If you’re running everything on a single machine, you’re leaving speed on the table.&lt;/p&gt;

&lt;p&gt;Example with 200 tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 machine → ~20 minutes&lt;/li&gt;
&lt;li&gt;4 machines → ~5 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same amount of tests. Same coverage. Same value. Just distributed.&lt;/p&gt;

&lt;p&gt;If you’re not parallelizing, you’re choosing to be slow.&lt;/p&gt;

&lt;p&gt;Most CI providers support this out of the box and Cypress provides it with integration into &lt;a href="https://docs.cypress.io/cloud/features/smart-orchestration/parallelization" rel="noopener noreferrer"&gt;Cypress Cloud&lt;/a&gt;. Without Cloud there are free plugins that achieve the same thing, such as &lt;a href="https://github.com/bahmutov/cypress-split" rel="noopener noreferrer"&gt;cypress-split&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Pipeline Is Only As Fast As the Slowest Spec
&lt;/h3&gt;

&lt;p&gt;Parallelization helps, until one, pesky spec decides to ruin everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec A → 12 minutes  
spec B → 3 minutes  
spec C → 3 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your pipeline time? Yeah, 12 minutes.&lt;/p&gt;

&lt;p&gt;This is where a lot of teams get tripped up. They split tests by file count instead of runtime.&lt;/p&gt;

&lt;p&gt;Knowing your slowest spec lets you target the biggest performance bottlenecks directly instead of guessing.&lt;/p&gt;

&lt;p&gt;Focus on outliers. Break them up if possible, or optimize what’s inside them and then track again. You should always know your slowest tests and why they’re slow.&lt;/p&gt;

&lt;p&gt;If possible, all specs should execute within a ~1 minute threshold of one another to ensure scaleable balance as the test suite grows and additional resources are to your parallelization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Cypress and Node Modules
&lt;/h3&gt;

&lt;p&gt;CI loves to pretend it’s starting from scratch every run.&lt;/p&gt;

&lt;p&gt;Downloading Cypress. Installing dependencies. Setting up browsers.&lt;/p&gt;

&lt;p&gt;But want to know a secret? You don’t need to pay that cost every time.&lt;/p&gt;

&lt;p&gt;Cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;node_modules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cypress binary&lt;/li&gt;
&lt;li&gt;browser dependencies (if applicable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of those optimizations that feels boring but it shaves minutes off every single run. Always review best practices around CI and your CI provider to ensure your pipeline isn’t dragging unintentionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Persisting Artifacts for Passing Tests
&lt;/h3&gt;

&lt;p&gt;Videos and screenshots are incredibly useful when something breaks.&lt;/p&gt;

&lt;p&gt;When everything passes? They’re just expensive souvenirs.&lt;/p&gt;

&lt;p&gt;If you’re uploading artifacts for every run, you’re increasing pipeline time, increasing storage costs, and making it harder to find what actually matters.&lt;/p&gt;

&lt;p&gt;Turn them off for green runs. Keep them for failures where they’re actually useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Startup Optimizations
&lt;/h2&gt;

&lt;p&gt;Some of the worst delays don’t come from the tests themselves, they happen before the first test even runs.&lt;/p&gt;

&lt;p&gt;And most teams miss it, because they’re focused on runtime, not startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use APIs for Test Setup
&lt;/h3&gt;

&lt;p&gt;UI data setup is the scenic route. And trust me, I love a gorgeous view just like the next person, but our end-to-end tests don’t need the extra travel.&lt;/p&gt;

&lt;p&gt;Let’s pretend we want to test deleting a “project” from our application.&lt;/p&gt;

&lt;p&gt;This is what it often looks like, simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=email]&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;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=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;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=login]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=create-project]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=project-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;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;Test Project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy=submit]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without realizing it, our test is validating the create flow and dependent on that UI for delete functionality that should be isolated. Expand this and a longer UI-setup might take 5–10 seconds just to set state. Multiply that across your suite and the cost becomes obvious.&lt;/p&gt;

&lt;p&gt;Instead, utilize &lt;a href="https://docs.cypress.io/api/commands/request" rel="noopener noreferrer"&gt;cy.request&lt;/a&gt; to setup the state programatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&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/projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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 setup runs in milliseconds instead of seconds. I wrote about this topic in-depth in a blog &lt;a href="https://medium.com/@dingraham01/cypress-programmatically-control-test-data-e783aa02dc34" rel="noopener noreferrer"&gt;here&lt;/a&gt; and how to easily achieve this pattern at scale in your test suite.&lt;/p&gt;

&lt;p&gt;In short, use the UI when you’re testing the UI.&lt;br&gt;
Not when you’re just setting the stage to get to real value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Heavy Imports in Support Files
&lt;/h3&gt;

&lt;p&gt;Every Cypress spec loads your support files.&lt;br&gt;
Simply, whatever you import there, runs every time.&lt;/p&gt;

&lt;p&gt;If you’re pulling in half your project globally, you’ve created a startup tax.&lt;br&gt;
For clarification, here’s a &lt;a href="https://github.com/cypress-io/cypress/issues/25533" rel="noopener noreferrer"&gt;github issue&lt;/a&gt; that explains this very problem.&lt;/p&gt;

&lt;p&gt;Just like everything else we’ve mentioned, it adds seconds to every spec file. So only load what you actually need.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move heavy imports into specific tests&lt;/li&gt;
&lt;li&gt;lazy-load where possible&lt;/li&gt;
&lt;li&gt;keep your support file lean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an &lt;a href="https://dev.to/muratkeremozcan/improve-cypress-e2e-test-latency-by-a-factor-of-20-34ce?utm_source=chatgpt.com"&gt;in-depth article&lt;/a&gt; from the talented &lt;a href="https://www.linkedin.com/in/murat-ozcan-3489898/" rel="noopener noreferrer"&gt;Murat Ozcan&lt;/a&gt; that explains this concept in expanded details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review Your Preprocessor
&lt;/h3&gt;

&lt;p&gt;Preprocessing is one of those hidden slowdowns most teams don’t think about. Essentially, bundling (or preprocessing) is the step where Cypress compiles and transforms your test files and their dependencies into browser-ready JavaScript before execution.&lt;/p&gt;

&lt;p&gt;And in short, if that step is slow, everything is slow.&lt;/p&gt;

&lt;p&gt;Cypress uses Webpack by default and for the most part it works. Nearly all teams are using it for this very reason. However, it’s also not exactly &lt;a href="https://github.com/cypress-io/cypress/issues/25928?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;famous for being quick in large test setups&lt;/a&gt; and might struggle compared to alternative solutions.&lt;/p&gt;

&lt;p&gt;If your suite has grown, it’s worth looking at faster preprocessors like &lt;a href="https://glebbahmutov.com/blog/fast-esbuild/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt;(another fantastic video by Murat &lt;a href="https://www.youtube.com/watch?v=Hc_3oLpayOY" rel="noopener noreferrer"&gt;here&lt;/a&gt;) or Vite (using &lt;a href="https://github.com/mammadataei/cypress-vite" rel="noopener noreferrer"&gt;cypress-vite&lt;/a&gt;). In a lot of projects, that switch alone noticeably cuts startup time.&lt;/p&gt;

&lt;p&gt;If you are noticing significant performance latency just starting Cypress before tests run, then it might be time to reconsider your preprocessor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review Your Config Tip
&lt;/h3&gt;

&lt;p&gt;Finally, sometimes the slowdown isn’t hiding in your tests at all, it’s sitting in your &lt;code&gt;cypress.config&lt;/code&gt; quietly adding overhead to every run.&lt;/p&gt;

&lt;p&gt;There are a few settings that are worth revisiting with performance in mind: &lt;code&gt;retries&lt;/code&gt;, &lt;code&gt;video&lt;/code&gt;, &lt;code&gt;screenshots&lt;/code&gt;, and &lt;code&gt;numTestsKeptInMemory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Retries are the easiest one to overlook. They feel helpful, and they also hide flakiness and multiply runtime. A test that passes on the third attempt didn’t just pass, it ran three times.&lt;/p&gt;

&lt;p&gt;Video and screenshot settings fall into a similar category. They’re incredibly useful when something breaks, but they don’t always need to be enabled for every passing test.&lt;/p&gt;

&lt;p&gt;The point isn’t to turn everything off. It’s to be intentional.&lt;/p&gt;

&lt;p&gt;It’s also worth staying reasonably up to date with Cypress itself. Performance improvements do get shipped, and running an older version longer than you need to can quietly hold you back in ways that have nothing to do with your tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Tip: Be Data-Driven About Your Problem Tests
&lt;/h2&gt;

&lt;p&gt;If you don’t measure it, it will rot.&lt;br&gt;
Test suites don’t stay fast by accident.&lt;/p&gt;

&lt;p&gt;You need to know where your time is going: your slowest tests, your slowest specs, and your flakiest tests.&lt;/p&gt;

&lt;p&gt;That’s where the real problems live.&lt;/p&gt;

&lt;p&gt;Otherwise, you’re guessing — and guessing doesn’t scale.&lt;/p&gt;

&lt;p&gt;In most suites, a small number of slow or flaky tests are doing most of the damage. That’s where you start. Fixing your worst offenders will move the needle far more than tweaking 50 “okay” tests. Use data to identify what’s actually hurting you, surface flaky tests causing retries, and keep an eye on runtime drift over time.&lt;/p&gt;

&lt;p&gt;Then fix what matters.&lt;/p&gt;

&lt;p&gt;That’s how you get faster pipelines, more reliable results, and a test suite people actually trust.&lt;/p&gt;

&lt;p&gt;For help identifying where your test flake is occurring, &lt;a href="https://www.linkedin.com/in/sebastianclavijosuero/" rel="noopener noreferrer"&gt;Sebastian Suero&lt;/a&gt; has a great plugin that can help with that, &lt;a href="https://dev.to/sebastianclavijo/cypress-flaky-testaudit-thriving-in-the-cypress-dual-verse-for-once-l4o"&gt;cypress-flaky-test-audit&lt;/a&gt;. And for how to build your own merge report to track these metrics, you can find my tutorial &lt;a href="https://medium.com/@dingraham01/cypress-how-to-create-a-merge-report-in-your-pipeline-a52dc02190a7" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final, Final Tip: Does It Need to Be End-to-End?
&lt;/h2&gt;

&lt;p&gt;The hard truth is, after all these other tips, a lot of slow suites are slow because end-to-end tests are carrying work that does not actually belong there. This happens all the time on startup teams, or on teams where QA sits in its own lane and E2E becomes the default place to validate everything.&lt;/p&gt;

&lt;p&gt;Cypress supports both end-to-end and component testing, and &lt;a href="https://docs.cypress.io/app/core-concepts/testing-types?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;it's docs&lt;/a&gt; are pretty clear that different test types exist for different kinds of confidence. If a check can be proven faster at the API or component level, that is usually the better place for it. That does not mean E2E is less valuable. It just means it should be used where it shines: proving real user flows, not carrying the entire quality strategy on its back.&lt;/p&gt;

&lt;p&gt;Cypress can also be used for API-style testing, and community plugins like &lt;a href="https://github.com/filiphric/cypress-plugin-api" rel="noopener noreferrer"&gt;cypress-plugin-api&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/filip-hric/" rel="noopener noreferrer"&gt;Filip Hric&lt;/a&gt;, build on that by adding a &lt;code&gt;cy.api&lt;/code&gt; command for API-focused workflows in the Cypress runner.&lt;/p&gt;

&lt;p&gt;On the UI side, &lt;a href="https://docs.cypress.io/app/component-testing/get-started" rel="noopener noreferrer"&gt;Cypress Component Testing&lt;/a&gt; gives you a way to validate rendering, state, and interaction at the component layer, which is often much faster and more targeted than proving the same thing through a full browser journey. A healthy suite usually has a mix of all three.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Most slow test suites don’t collapse because of one bad decision. They decay over time. A login repeated too often, a wait no one questioned, a setup flow that felt “good enough.” Individually harmless, but together they quietly drag down your feedback loop.&lt;/p&gt;

&lt;p&gt;And once that loop breaks, your tests stop shaping behavior. Failures show up too late, context is lost, and fixing issues becomes slower and more painful than it needs to be.&lt;/p&gt;

&lt;p&gt;The most valuable moment for a failure isn’t ten minutes later in CI. It’s right after you push, while everything is still fresh.&lt;/p&gt;

&lt;p&gt;Fast tests protect that moment. Slow tests erase it.&lt;/p&gt;

&lt;p&gt;With that, happy testing.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>productivity</category>
      <category>testing</category>
    </item>
    <item>
      <title>Lessons I Wish I Knew When I Started in Test Automation</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Wed, 15 Apr 2026 00:26:22 +0000</pubDate>
      <link>https://dev.to/cydavid/lessons-i-wish-i-knew-when-i-started-in-test-automation-13mn</link>
      <guid>https://dev.to/cydavid/lessons-i-wish-i-knew-when-i-started-in-test-automation-13mn</guid>
      <description>&lt;p&gt;&lt;em&gt;A Battle-Scarred Senior SDET's Perspective&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I first moved into test automation, I thought I was doing well.&lt;/p&gt;

&lt;p&gt;I could write clean code. I built frameworks I felt proud of. Most of my pipelines were green. I was even catching the occasional bug before customers ever saw it. At the time, that felt like success.&lt;/p&gt;

&lt;p&gt;Looking back, that view was narrow. I measured myself by output instead of impact. I focused on what I could build, not on how that work helped teams ship safely or make better decisions. "Catching bugs" became the goal, rather than understanding risk.&lt;/p&gt;

&lt;p&gt;Over time though, I saw the same problems repeat across teams, tools, and companies. The details changed. The patterns did not.&lt;/p&gt;

&lt;p&gt;Automation is not about tests that pass. It is about helping teams understand risk and and ship with confidence. Tests are just one slice of a larger system and if you only focus on the test results, you'll miss the bigger picture entirely like I did early on.&lt;/p&gt;

&lt;p&gt;With that in mind, these are the lessons I wish I had learned sooner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Strong coding skills are necessary, but not sufficient
&lt;/h2&gt;

&lt;p&gt;My path into QA was highly unconventional.&lt;/p&gt;

&lt;p&gt;I started as a full-stack developer after graduating with a computer science degree. My mindset was code first, code always. I fell into QA almost by accident. Let's grab a coffee sometime and trade career stories. I'm grateful it happened, but early on, my identity lived entirely in the code. I focused almost obsessively on writing elegant test frameworks. Clean architecture. Reusable helpers. Beautiful setup APIs. Everything pristine. If the framework looked slick, I felt successful.&lt;/p&gt;

&lt;p&gt;Now don't get me wrong, that work matters. Strong coding skills are required for test suites that scale, stay readable, and survive change. You cannot build serious automation without them. But code should not be the first focus. It is a tool, not the goal.&lt;/p&gt;

&lt;p&gt;Strong code can hide weak thinking, and nobody ever taught me how to think about quality. That is not something you learn in school.&lt;/p&gt;

&lt;p&gt;You can write clean tests and still miss the failures that hurt users. Effective testing starts with understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;risk&lt;/li&gt;
&lt;li&gt;system behavior&lt;/li&gt;
&lt;li&gt;where failures are likely to occur&lt;/li&gt;
&lt;li&gt;business impact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you shift from solely focusing on writing code to thinking about the true failure, your impact changes. The code supports the thinking, not the other way around. This is why I believe so many developers struggle to write effective automation tests. They can code just fine, but the thinking can be a bit shallow. And when the thinking lacks depth, you end up with a very nice test that ultimately delivers no business value.&lt;/p&gt;

&lt;p&gt;It took me a long time to learn this. I still take pride in my technical skills, but I learned the hard way that strong automation starts with a strong quality mindset. No amount of clever code can compensate for the absence of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How flaky tests are handled reflects leadership
&lt;/h2&gt;

&lt;p&gt;The first time someone said "just rerun it," I didn't push back.&lt;/p&gt;

&lt;p&gt;It was "one of those tests." A little finicky. We joked that it was having a bad day and needed more coffee. Rerun it, get green, move on. It seems so insignificant at the time but over the years I've realized this mindset is quite damaging.&lt;/p&gt;

&lt;p&gt;When teams accept flakes, failures lose meaning. Engineers stop trusting results. Automation turns into noise. You still run the tests, but you stop taking them seriously.&lt;/p&gt;

&lt;p&gt;Let's be clear, flaky tests do not reflect weak engineers. They reflect weak decisions about ownership and priorities. Flake will always exist. You cannot eliminate it completely. It is a by-product of distributed systems, async behavior, environments, data, and timing.&lt;/p&gt;

&lt;p&gt;Leadership is not just about preventing flake. It is about how you respond when it happens.&lt;/p&gt;

&lt;p&gt;Ignoring flakes, hiding them, or rerunning until green are leadership choices. Fixing them requires explicit ownership and real trade-offs. The moment you decide to tolerate noise in your test suite is the moment you accept a brittle quality culture.&lt;/p&gt;

&lt;p&gt;At a senior level, this becomes your responsibility. Flakiness must be treated like any other defect, with root cause, follow-up, and prevention. You must protect your green runs and address any failure, of any type, with urgency. Otherwise, over time, if you don't enforce this, you slowly lower your quality bar without realizing it.&lt;/p&gt;

&lt;p&gt;This may sound extreme, but pause and think about it. I genuinely believe flaky tests are worse than having no tests at all. Noise distracts teams from work that matters. Being complacent with flakes is a subtle form of accepting failure. One path is easy. The other leads to long-term reliability and trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  CI pipelines matter as much as the tests
&lt;/h2&gt;

&lt;p&gt;I didn't realize it early in my career but how your tests run is just as important as how they are written.&lt;/p&gt;

&lt;p&gt;You can have the most perfectly stable test that runs locally and passes 100/100 times but if you don't put the same effort and thinking into the pipeline, what you will end up seeing is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow execution&lt;/li&gt;
&lt;li&gt;unclear failures&lt;/li&gt;
&lt;li&gt;unreliable test results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now all of a sudden, a test suite that was valuable becomes noise and a giant liability.&lt;/p&gt;

&lt;p&gt;And it only get worse. When pipelines are slow or confusing, teams stop using automation as a decision tool, which is their entire purpose. Now something that used to be valuable turns into a never-ending abyss of tech debt. Tests must be fast and predictable and I learned this the hard way.&lt;/p&gt;

&lt;p&gt;In my first role as an SDET, I focused on building a strong local test suite from scratch. Within a month, I had around 50 E2E tests covering most of the application's functionality. They looked great locally. They passed reliably. When I finally hooked them up to CI, everything fell apart. The same tests were now running against different environments, different data, and different performance factors. Instead of building confidence, I suddenly had 50 unstable tests and no pipeline maturity to support them. I ended up retroactively putting out 50 fires instead of starting with a small, reliable subset and building the pipeline correctly from the beginning.&lt;/p&gt;

&lt;p&gt;I wish I had spent more time early on learning CI/CD fundamentals and pipeline best practices. Turning a local test suite into a reliable pipeline powerhouse takes skill, and it's a skillset I underestimated for far too long.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automation is a product and maintenance is the work
&lt;/h2&gt;

&lt;p&gt;This lesson took me longer than I care to admit.&lt;/p&gt;

&lt;p&gt;If you start to think about your automation test suite from the same perspective as any product, something starts to change. You quickly realize that you have users just like your business has customers. In this case, those users are developers.&lt;/p&gt;

&lt;p&gt;Once you accept that your automation has users, you start designing for them. Readability matters. Supportability matters. Stability matters. Test code is not second-class code. It runs in production pipelines, blocks releases, and influences real decisions. Treating it as "just test code" is how suites rot over time.&lt;/p&gt;

&lt;p&gt;Just like a real product, if it is broken, unreliable, or constantly lying, nobody will use it. Treat automation like a real product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define ownership&lt;/li&gt;
&lt;li&gt;classify failure types and severities&lt;/li&gt;
&lt;li&gt;track stability and runtime&lt;/li&gt;
&lt;li&gt;plan improvements over time with ticketing and planning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production systems have roadmaps, metrics, and refactoring cycles. Automation needs the same discipline to stay useful.&lt;/p&gt;

&lt;p&gt;In QA, we expect the product to meet a high quality bar. If we do not hold our own automation to the same standard, what message are we sending?&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging is a skill that must be built on purpose
&lt;/h2&gt;

&lt;p&gt;Debugging is rarely taught.&lt;/p&gt;

&lt;p&gt;Most people learn it retroactively through repetition, frustration and mistakes. At this point in my career, troubleshooting is one of the biggest gaps I see between junior and senior engineers.&lt;/p&gt;

&lt;p&gt;Debugging is a mindset. While experience helps, you do not need decades to improve it. You can reshape how you debug today.&lt;/p&gt;

&lt;p&gt;I like to think of debugging as being equivalent to solving a murder mystery. As a detective, is it easier to solve the case with a hundred suspects or three? The same is true with test failures. How you limit suspects…or variables, matters. This is why starting from a known-good baseline is invaluable. If everything is already broken, you are not debugging. You are firefighting.&lt;/p&gt;

&lt;p&gt;Senior engineers practice debugging intentionally. They reflect on their approach. They form hypotheses, rule things out, and trace failures across layers. Debugging deserves the same respect as framework design or test strategy and it's often overlooked until the moment it's needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;It took me years to learn these lessons. In some ways, I'm still learning them.&lt;/p&gt;

&lt;p&gt;I've come to appreciate that feeling. It means my assumptions are still being tested. My thinking is still evolving. My feedback loop is still alive.&lt;/p&gt;

&lt;p&gt;In a way, it's my own continuous integration. Every failure teaches me something. Every correction makes the system a little stronger.&lt;/p&gt;

&lt;p&gt;If I could go back, I wouldn't tell myself to learn another tool.&lt;/p&gt;

&lt;p&gt;I mean, I still would. Tools are fun.&lt;/p&gt;

&lt;p&gt;But it wouldn't be my first priority.&lt;/p&gt;

&lt;p&gt;I would say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;learn how systems fail&lt;/li&gt;
&lt;li&gt;learn how teams make decisions&lt;/li&gt;
&lt;li&gt;learn how to influence without authority&lt;/li&gt;
&lt;li&gt;learn how to think in "quality" rather than thinking in test results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical skills grow with time. Impact comes from everything around them.&lt;/p&gt;

&lt;p&gt;If you are early in your career and reading this, you are doing better than you think. Feeling uncertain often means you are learning faster than your confidence can keep up.&lt;/p&gt;

&lt;p&gt;Even senior engineers feel it.&lt;br&gt;&lt;br&gt;
Especially the ones who say they do not.&lt;/p&gt;

&lt;p&gt;Remember, good automation doesn't just catch bugs. It helps teams make better decisions.&lt;/p&gt;

&lt;p&gt;Happy testing and learning.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Build a QA Team Engineers Actually Want to Work With</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Wed, 15 Apr 2026 00:05:33 +0000</pubDate>
      <link>https://dev.to/cydavid/how-to-build-a-qa-team-engineers-actually-want-to-work-with-34d7</link>
      <guid>https://dev.to/cydavid/how-to-build-a-qa-team-engineers-actually-want-to-work-with-34d7</guid>
      <description>&lt;p&gt;&lt;em&gt;Why quality teams fail, what modern QA actually looks like, and how to build a culture developers trust and want to collaborate with.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever joined a software team where QA was treated like the final boss battle before release, the place where tickets go to die, then you know one of the quiet truths of software development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teams don’t struggle with quality because testing is bad.&lt;br&gt;
They struggle because the relationship around testing is broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Engineers love quality.&lt;br&gt;
Product managers love quality.&lt;br&gt;
Leadership definitely loves quality.&lt;/p&gt;

&lt;p&gt;And yet, despite all that love, “QA” somehow becomes the team people avoid until the very end of the cycle. Not because QA is slow or unhelpful, but because the way we integrate QA into development sets everyone up for friction. When the only time QA enters the conversation is during the final days of a sprint, everything QA finds becomes a roadblock, even if it’s exactly what the team needs to hear.&lt;/p&gt;

&lt;p&gt;So the real question isn’t:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we test more, earlier?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we build a QA team engineers actually want to work with?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s a cultural question, not a technical one.&lt;br&gt;
And like any cultural shift, the answer begins long before the first test is ever written.&lt;/p&gt;




&lt;h2&gt;
  
  
  QA as Partners, Not Police
&lt;/h2&gt;

&lt;p&gt;For years, many QA teams operated under a “late-gate” model, brought in only at the end, tasked with catching what slipped through earlier. That structure often meant QA inherits all risk, all bugs, and all last-minute fire drills.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.testrail.com/" rel="noopener noreferrer"&gt;TestRail’s 2025 industry report&lt;/a&gt; , &lt;strong&gt;late involvement is still one of the top challenges for QA teams&lt;/strong&gt;, even inside organizations with modern tooling. While nearly &lt;a href="https://www.businesswire.com/" rel="noopener noreferrer"&gt;40% of teams report&lt;/a&gt; shifting QA earlier in some regards, to address this, a majority still operate inside legacy patterns.&lt;/p&gt;

&lt;p&gt;Teams that do shift early see measurable benefits. Those that embed QA early and combine it with automation and CI/CD not only ship faster, they ship with fewer production defects. The improvement isn’t theoretical; it shows up directly in velocity and stability.&lt;/p&gt;

&lt;p&gt;That means the problem isn’t just &lt;strong&gt;who&lt;/strong&gt; finds the bug, it’s &lt;strong&gt;when&lt;/strong&gt; the bug is found and whether QA is positioned as a safety net or a strategic collaborator.&lt;/p&gt;

&lt;p&gt;Safety nets catch the fall.&lt;br&gt;
Partners prevent the fall.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Story: When Early Collaboration Changes Everything
&lt;/h2&gt;

&lt;p&gt;In a &lt;a href="https://www.youtube.com/" rel="noopener noreferrer"&gt;recent talk&lt;/a&gt; at CypressConf, I shared a success story that changed the way I think about QA and engineering collaboration.&lt;/p&gt;

&lt;p&gt;At my current company, before I joined, QA consisted of a single manual engineer working an eight-hour time difference from the rest of the team. He was excellent, detail-oriented, sharp, and reliable. But the system around him wasn’t. The team constantly faced unpredictable bugs, long regressions, and slow loops of quality feedback. Not because he lacked skill, but because &lt;strong&gt;the process wasn’t built for scale or speed&lt;/strong&gt;. And in startups, time is money. If you can’t learn fast, you can’t survive long.&lt;/p&gt;

&lt;p&gt;So my mission was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bring automation.&lt;/li&gt;
&lt;li&gt;Build a &lt;a href="https://www.browserstack.com/" rel="noopener noreferrer"&gt;Shift Left&lt;/a&gt; process.&lt;/li&gt;
&lt;li&gt;Create a culture where everyone, not just QA, owns quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This began with addressing the current team mentality because the hard truth is, the tool you use doesn’t matter if the mindset’s wrong. If the team doesn’t have &lt;strong&gt;accountability and buy-in&lt;/strong&gt;, no framework or tool is going to save you. It sounds simple but it really isn’t. When deadlines tighten, people cut corners, skip conversations, push code as fast as possible and the first thing to diminish is testing and quality. It’s seen as a blocker, something that slows you down instead of helping you move faster.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;However, Shifting Left isn’t about slowing down at all. It’s about creating the conditions that allow you to sprint consistently long-term. It’s about building a culture where mistakes are safe, where people trust one another, and where failing fast becomes a competitive advantage.&lt;/p&gt;

&lt;p&gt;Because if we fail fast, we learn fast.&lt;br&gt;
And if we learn fast, we improve fast.&lt;/p&gt;

&lt;p&gt;So that’s where I started. I got to know my coworkers, understood their roles, responsibilities, pain points, and personal definitions of quality. Then together we defined a process everyone agreed on before writing a single line of code or a single test. This early alignment removed future friction, surprises and pushback. People weren’t being forced into a new process; they were excited to build it and be a part of it.&lt;/p&gt;

&lt;p&gt;Once that mindset was in place, automation amplified it. Suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product and QA collaborated on risks before development began&lt;/li&gt;
&lt;li&gt;Developers proactively asked QA for early input&lt;/li&gt;
&lt;li&gt;QA wrote automation in parallel with development&lt;/li&gt;
&lt;li&gt;Test failures became team conversations&lt;/li&gt;
&lt;li&gt;Flaky tests turned into puzzles, not headaches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool didn’t fix the culture.&lt;br&gt;
&lt;strong&gt;The collaboration did.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that’s the version of QA developers love working with: the team that helps them ship with &lt;strong&gt;confidence&lt;/strong&gt;, not &lt;strong&gt;compliance&lt;/strong&gt;. Together. But even with a collaborative culture, there’s one moment where QA and engineering collide the hardest…bugs and issues.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineers Want Feedback, Not Noise
&lt;/h2&gt;

&lt;p&gt;With all this talk about partnership, we have to address the moment where QA and engineering collide the most: &lt;strong&gt;feedback&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There’s a misconception in tech that engineers dislike feedback. Rather, they dislike feedback that is late, vague, incomplete or creates more confusion than clarity.&lt;/p&gt;

&lt;p&gt;High-quality QA feedback should be a service, not a verdict. And the way to measure that service is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this feedback reduce uncertainty or increase it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.testrail.com/" rel="noopener noreferrer"&gt;TestRail’s 2025 report&lt;/a&gt; found that unclear bug reports remain one of the top workflow inefficiencies for developers, a quiet tax teams pay sprint after sprint. Let’s take a look at a quick example on what bad vs good feedback might look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Bad (Noise) Bug Report&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title&lt;/strong&gt;: Checkout broken&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Description&lt;/strong&gt;: Tried to check out — didn’t work.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Steps&lt;/strong&gt;: Click checkout button&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Expected&lt;/strong&gt;: Should work&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Actual&lt;/strong&gt;: Didn’t&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Notes&lt;/strong&gt;: Happens sometimes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, this might be a bit of an exaggerated example but the point is hopefully clear. Noise reports don’t &lt;em&gt;look&lt;/em&gt; noisy. Noise just means that it lacks everything that matters. The noise happens afterward, in the extensive detective work, ambiguity, Slack threads, and frustration it triggers amongst everyone.&lt;/p&gt;

&lt;p&gt;This report doesn’t save time, it &lt;strong&gt;creates&lt;/strong&gt; new work. Compare that to…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Good (High-Fidelity) Bug Report&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title&lt;/strong&gt;: Checkout fails on second payment attempt due to stale session token&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Environment&lt;/strong&gt;: Staging, Safari&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Steps&lt;/strong&gt;:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add item to cart  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter payment info  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Double-click “Pay” in rapid succession  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected&lt;/strong&gt;: Duplicate clicks should be ignored&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Actual&lt;/strong&gt;: Second click sends a stale token → 409 Conflict&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Impact&lt;/strong&gt;: Revenue blocker; affects all Safari users&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Artifacts&lt;/strong&gt;: Screenshot, video, attached&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference is clear. A High-fidelity feedback builds reliability. Reliability builds trust. Then trust unlocks collaboration. This report allows QA to set the engineering team up for success and mitigate any issues without unnecessary bug clarification.&lt;/p&gt;

&lt;p&gt;And this level of clarity shouldn’t just stop at bugs. The best QA teams surface patterns that engineers can’t see from inside the code, like subtle regressions, design inconsistencies, brittle user flows, and scaling issues.&lt;br&gt;&lt;br&gt;
Collaborative teams don’t view this feedback as nitpicking. Rather it’s welcomed as insight. It’s the kind of feedback engineers actively want because it makes their work easier, their decisions safer, and their releases smoother.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Side of Trust: What Good Automation Looks Like
&lt;/h2&gt;

&lt;p&gt;Up to this point we’ve talked about partnership, and culture, but there’s another dimension that defines whether engineers actually trust (and enjoy working with), &lt;strong&gt;your automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can be empathetic, collaborative, and proactive, but if your test suite fails for reasons no one can explain, or breaks every other run, you lose trust immediately. Developers won’t say it out loud, but they think it…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why are the tests breaking again?”&lt;br&gt;&lt;br&gt;
“Is this real or flaky?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A QA team engineers love working with understands that trust isn’t just relational, it’s technical. The relationship sets the foundation, then the technical reinforces it.&lt;/p&gt;

&lt;p&gt;Shifting Left is all about thinking ahead; focusing on the high-level so the low-level execution goes smoothly. Test automation is no different and strong automation starts long before a single test is written, the foundation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start With the Foundation, Not the Test Count
&lt;/h3&gt;

&lt;p&gt;Many teams rush into automation believing velocity comes from quantity. It doesn’t. A massive test suite built on a weak foundation becomes slow, brittle, and unpredictable. Developers stop trusting failures, and eventually stop looking at them altogether.&lt;br&gt;&lt;br&gt;
Scalable automation begins with intention: reusable helpers, a consistent test structure, clean patterns, solid test data strategy, environment stability, and early conversations about testability. When you invest in the foundation first, reliability stops being an aspiration and becomes the default. Flakes drop. Debugging speeds up. Adding new tests becomes painless. And suddenly, your automation suite becomes what it was always meant to be, a fast, trustworthy source of feedback.&lt;br&gt;&lt;br&gt;
Remember, Shift Left isn’t just about testing earlier; it’s about hearing the truth earlier. And you can only do that when you’re running tests you actually trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protect the Green Baseline
&lt;/h3&gt;

&lt;p&gt;Once you’ve built a foundation you can trust, the next step is protecting it relentlessly. A reliable suite only stays reliable when the team treats every failure with the same urgency, no matter the cause. Flakiness isn’t just an annoyance, it’s a threat to the credibility of your entire pipeline.&lt;br&gt;&lt;br&gt;
Keep failures meaningful. Obsess over the green baseline, over every failure. Treat your test code with the same respect as application code. You wouldn’t knowingly ship broken software to your users; well, with automation, the engineering team is the customer and now you have full control over what they experience.&lt;br&gt;&lt;br&gt;
When QA and engineering hold each other accountable, not merging code with broken tests, not hand-waving flaky failures, not letting noise creep in, the suite becomes something developers rely on instinctively. And when failures mean something, automation becomes one of the most trusted parts of your delivery process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make Debugging Effortless to Understand
&lt;/h3&gt;

&lt;p&gt;The next layer of trust comes from visibility. When a test fails, people need to understand what happened without diving into a labyrinth of logs and guesswork. Good automation quickly hands people the answer, or at least the trail of breadcrumbs.&lt;br&gt;&lt;br&gt;
Clear videos. Screenshots. Network logs. A readable stack trace. A test title that sounds like a clear sentence. When context is immediate and available, anyone can help troubleshoot, developers, QA, even that one intern if they’re feeling brave.&lt;br&gt;&lt;br&gt;
Debugging shouldn’t feel like assembling Ikea furniture without the manual so invest in your debugging experience. Transforms failures from individual headaches into shared conversations. When QA empowers developers with clarity, developers feel empowered to help QA in return. It becomes a loop where visibility fuels collaboration.&lt;/p&gt;

&lt;h2&gt;
  
  
  QA in Every PR
&lt;/h2&gt;

&lt;p&gt;One of the most effective collaboration practices I’ve ever been a part of was a lightweight rule: &lt;strong&gt;every PR requires QA approval before merge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, that might raise some eyebrows, so let me clarify what it actually means.&lt;br&gt;&lt;br&gt;
In this model, QA isn’t a gatekeeper, and they’re definitely not blockers. They’re not doing a full engineering-style review or going line by line through the diff (though that can still happen when it’s useful). Instead, QA acts as the team’s final “quality check”, a quick conversation to confirm the change is genuinely ready to become part of the product.&lt;/p&gt;

&lt;p&gt;What QA actually looks for is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are all relevant automated tests passing?&lt;/li&gt;
&lt;li&gt;Were any new tests added where they should be?&lt;/li&gt;
&lt;li&gt;Has QA pulled the branch and validated the change locally?&lt;/li&gt;
&lt;li&gt;Is there anything surprising here that needs to be discussed before merge?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This small practice shifts quality left in the most natural way possible because &lt;strong&gt;every change now gets a quality conversation before it lands in the main branch&lt;/strong&gt;. It eliminates merge surprises. It ensures everyone stays aligned. And it forces QA and engineering to work closely together on everything, creating a genuine partnership.&lt;br&gt;&lt;br&gt;
Most importantly, the conversation is fast. Sometimes it takes a minute. Sometimes it takes a second. Some changes don’t need new tests or deep verification. Others spark deeper discussions, quick brainstorming, or small improvements that would’ve become big problems later. Either way, this tiny ritual prevents hours of rework from untested merges, ensures automation grows &lt;em&gt;with&lt;/em&gt; the product instead of becoming tech debt, and creates shared accountability, every change becomes everyone’s change.  &lt;/p&gt;

&lt;p&gt;And that’s the real shift: quality is no longer something that happens “after development.” It happens with development, on every branch, in every PR, through one simple conversation at a time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Across every team and every industry shift, one truth has held steady:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Quality is not something you do at the end.&lt;br&gt;
It’s something you build into the relationship.  *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my experience, quality cultures emerge from people &lt;strong&gt;who care enough to collaborate&lt;/strong&gt;. Meaning, a great QA team isn’t defined by how many bugs they find or how many tests they automate. They’re defined by how deeply they align with the team around them, how clearly they communicate, how early they collaborate, and how consistently they help people do their best work.  &lt;/p&gt;

&lt;p&gt;The teams that thrive know how to measure their progress. They look for friction, patterns in failures, breakdowns in communication, blind spots in automation, and when something isn’t working, they pivot early. They adjust, refine, and evolve instead of endure. &lt;/p&gt;

&lt;p&gt;And most importantly, they celebrate the wins because quality work is often invisible. They celebrate the first green pipeline. The first stable regression suite. The first time QA and dev debug a failure together. The first release that feels boring… in the best possible way.&lt;/p&gt;

&lt;p&gt;Celebration reinforces culture.&lt;br&gt;
Culture reinforces trust.&lt;br&gt;
Trust reinforces quality.&lt;/p&gt;

&lt;p&gt;And everything else flows from there.&lt;/p&gt;

&lt;p&gt;Be the QA that builds a quality culture. The kind that engineers want to work with.  &lt;/p&gt;

&lt;p&gt;As always, happy testing and even happier collaborating.&lt;/p&gt;

</description>
      <category>qa</category>
      <category>leadership</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>Test Leadership as Change Management: Why Your Real Job Isn’t Testing</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Mon, 03 Nov 2025 16:11:41 +0000</pubDate>
      <link>https://dev.to/cydavid/test-leadership-as-change-management-why-your-real-job-isnt-testing-27mh</link>
      <guid>https://dev.to/cydavid/test-leadership-as-change-management-why-your-real-job-isnt-testing-27mh</guid>
      <description>&lt;p&gt;If you’ve ever tried to roll out a new automation framework and been met with blank stares, quiet resistance, or the phrase “&lt;em&gt;we’ve always done it this way&lt;/em&gt;”, then you’ve already learned one of the hardest truths about QA leadership:&lt;/p&gt;

&lt;p&gt;Testing problems are rarely about testing.&lt;br&gt;
They’re about change.&lt;/p&gt;

&lt;p&gt;Every time we ask a team to adopt new tools, shift quality ownership earlier, or redefine what “done” means, we’re not just improving a process, we’re asking people to behave differently. And people don’t resist change because they’re stubborn; they resist it because it threatens what they know, how they work, and sometimes even how they define their value.&lt;/p&gt;

&lt;p&gt;As QA leaders, we don’t just test software.&lt;br&gt;
We test how well our teams can handle change.&lt;/p&gt;

&lt;p&gt;And now, we’re being tested more than ever because emerging technologies like AI aren’t just changing how we test, they’re changing what it means to be a tester. Especially in an age where a majority of teams &lt;a href="https://www.capgemini.com/wp-content/uploads/2024/10/WQR-24-MAIN-REPORT-CG.pdf" rel="noopener noreferrer"&gt;don’t view Quality Engineering as strategic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meaning, it’s more important than ever to redefine what test leadership really means.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Every QA Initiative Is a Change Initiative&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Think about what we actually do as QA leaders today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evolve testing practices&lt;/strong&gt; — Introduce automation over manual testing while defining the overall quality strategy and execution framework for engineers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift quality left&lt;/strong&gt; — Champion early involvement in design and development so issues are prevented, not just detected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridge teams and perspectives&lt;/strong&gt; — Act as the liaison between development, product, and leadership, redefining what “quality” means in a culture obsessed with speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coach and elevate others&lt;/strong&gt; — Develop testers into quality advocates, mentoring teams to own quality beyond their job titles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lead cultural change&lt;/strong&gt; — Create buy-in for new processes and tools, guiding teams through the emotional side of change rather than forcing compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those moves may involve tools and data but at their core, they’re human.&lt;/p&gt;

&lt;p&gt;They influence identity.&lt;br&gt;
They challenge comfort zones.&lt;br&gt;
They redefine what quality means in your organization.&lt;/p&gt;

&lt;p&gt;For the first time, tools aren’t just doing the work, they’re thinking alongside us. And that can feel unsettling. One of the most common reactions I hear isn’t fear of the tool, it’s fear of irrelevance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If this system can write tests, where do I fit in?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s why the best QA leaders don’t push change harder.&lt;br&gt;
They make it safer.&lt;/p&gt;

&lt;p&gt;They understand that leading through new technology isn’t about enforcing adoption, it’s about helping people rediscover their value in a changing landscape. After all, no tool replaces people… but the tester who can lead people through change will always stay ahead of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Emotional Curve of Change&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Whether you call it &lt;a href="https://in.indeed.com/hire/c/info/what-is-adkar-model?gad_source=1&amp;amp;gclid=Cj0KCQjwgpzIBhCOARIsABZm7vFvXcLh5qS8EciyA7YHoa4Fa_OsuqDhQjiue61eX5ZBACfuTrsjwKEaArKJEALw_wcB&amp;amp;gad_campaignid=15513873562&amp;amp;gbraid=0AAAAADfh6_vfamATg5HtxIPfCKmOYrwWt&amp;amp;aceid=&amp;amp;gclsrc=aw.ds" rel="noopener noreferrer"&gt;ADKAR&lt;/a&gt;, &lt;a href="https://whatfix.com/blog/kubler-ross-change-curve/" rel="noopener noreferrer"&gt;Kübler-Ross&lt;/a&gt;, or just “the five stages of rollout panic,” every team goes through a predictable emotional arc during transformation. ADKAR, focuses on the &lt;em&gt;practical&lt;/em&gt; side of adoption, what people need to succeed under change. While, Kübler-Ross, originally the grief model, explores the &lt;em&gt;emotional&lt;/em&gt; side, how people feel as they adapt.&lt;/p&gt;

&lt;p&gt;Together, they remind us that change is both logical and emotion and leaders must guide both.&lt;/p&gt;

&lt;p&gt;Here’s what this might look like in testing transformations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Denial / Awareness&lt;/strong&gt; — &lt;em&gt;“Our current tests are fine; change won’t actually happen.”&lt;/em&gt; People need clarity on why change is necessary — not just what’s changing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anger / Desire&lt;/strong&gt; — “&lt;em&gt;You’re replacing our work with scripts… or worse, an algorithm.”&lt;/em&gt; Leaders must turn frustration into motivation by showing personal benefit, not just team goals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bargaining / Knowledge&lt;/strong&gt; — &lt;em&gt;“Maybe this assistant can help a little bit such as flaky test triage.”&lt;/em&gt; Curiosity grows once people feel safe to experiment. Training and open demos accelerate this stage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depression / Capability&lt;/strong&gt; — &lt;em&gt;“I’m not sure this is working… maybe this was a mistake.”&lt;/em&gt; Fatigue or doubt as the initial excitement wears off. Hands-on support, coaching, and small wins rebuild confidence and capability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance / Verification&lt;/strong&gt; — &lt;em&gt;“Okay, this actually makes our jobs easier.”&lt;/em&gt; Hands-on learning builds competence and confidence. This is where adoption begins to stick.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;New technologies don’t change the curve, they compress it.&lt;br&gt;
The reactions are the same, but the speed and intensity are higher.&lt;/p&gt;

&lt;p&gt;That’s why the best QA leaders treat resistance not as rebellion but as data; signals that show where the team is on the curve and what support they need next.&lt;/p&gt;

&lt;p&gt;Instead of fighting resistance, they ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;“What’s confusing right now?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What’s the biggest risk you see in this change?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What would make this transition easier for you?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That empathy builds alignment faster than any roadmap ever will.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The QA Leader’s Change Toolkit&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So how do you actually lead through testing change without losing your team’s trust or momentum?&lt;/p&gt;

&lt;p&gt;Here’s what separates great change leaders from frustrated ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fear of losing control → Involve people early&lt;/strong&gt;
Co-design rollout plans or test data strategies with the team instead of dictating them. Early ownership creates commitment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling distrust → Start with pilot wins&lt;/strong&gt;
Don’t announce; experiment. Run small proofs of concept, gather real outcomes, then share those visible successes with the team. Get buy-in one step at a time instead of simply “do this”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change fatigue → Normalize iteration&lt;/strong&gt;
Make it clear that every rollout is version 1. Encourage feedback and show that nothing is permanent, it’s all about learning and refining, together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of communication → Narrate the journey&lt;/strong&gt;
Communicate updates regularly. Share what’s been tried, what failed, and what’s next. Transparency builds confidence faster than perfection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech skepticism → Humanize the role&lt;/strong&gt;
Frame intelligent tools as partners, not replacements. Emphasize that they handle the repetitive parts of testing so humans can focus on strategy, creativity, and context.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Communicate twice as much as you think you need to, then listen twice as hard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When people feel seen and heard, they’ll follow you anywhere, even through a full AI-driven transformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Culture Is Your Test Strategy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At some point, every QA leader realizes: tools are temporary, culture is permanent.&lt;/p&gt;

&lt;p&gt;A great testing culture doesn’t happen because of the right frameworks, dashboards, or even our new AI overlords. It happens because people believe in the why behind the practices you lead.&lt;/p&gt;

&lt;p&gt;For example, these technologies act as mirrors for your culture. If your team already values curiosity, learning, and experimentation, they’ll thrive with them. If they value control, certainty, and strict boundaries then those cracks will surface immediately.&lt;/p&gt;

&lt;p&gt;For example, a &lt;a href="https://www.mckinsey.com/capabilities/transformation/our-insights/common-pitfalls-in-transformations-a-conversation-with-jon-garcia" rel="noopener noreferrer"&gt;2022 McKinsey Transformation&lt;/a&gt; study found that 70% of transformation initiatives fail due to lack of employee adoption and cultural readiness, not strategy or technology. That’s why your biggest success metric isn’t how many AI-powered tests you generate, but rather how confidently your team adopts the next new thing that comes along.&lt;/p&gt;

&lt;p&gt;Meaning, when your culture learns to adapt, you stop leading projects; you start leading evolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary: Testing Change, Not Just Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The next time you lead a testing transformation, don’t start by asking: “What will we automate?”&lt;/p&gt;

&lt;p&gt;Start by asking: &lt;em&gt;“Who will this change impact — and how can I make it safe for them to succeed?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because behind every process update or new tool adoption is a person navigating uncertainty. Your role isn’t just to implement technology, it’s to create trust in the transition.&lt;/p&gt;

&lt;p&gt;And when it comes to AI or any emerging technology, the real question isn’t “How fast can we use it?”, it’s &lt;em&gt;“How can I help my team trust both the machine — and themselves — as they learn to lead alongside it?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every meaningful QA transformation begins with a leader who tests their own assumptions first. Because in the end, change management is quality assurance — for humans, and now, for the intelligent systems learning beside us. So the next time you lead a QA team, take a step back and try the human approach. Test yourself.&lt;/p&gt;

&lt;p&gt;And as always, happy testing, through it all.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>leadership</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Automation Maturity Pyramid</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Wed, 17 Sep 2025 14:11:36 +0000</pubDate>
      <link>https://dev.to/cydavid/the-automation-maturity-pyramid-56o1</link>
      <guid>https://dev.to/cydavid/the-automation-maturity-pyramid-56o1</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;The Automation Maturity Pyramid&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;How effective is your automation test suite? &lt;br&gt;
How impactful is it for your product and your team? &lt;br&gt;
Do you know how to grow your test suite without sacrificing quality and performance?&lt;/p&gt;

&lt;p&gt;These questions are surprisingly difficult to answer - especially when your entire suite feels like it's constantly on fire, your tests are untrustworthy, and production bugs are popping up like they're going out of style. (Just me?)&lt;/p&gt;

&lt;p&gt;To bring some clarity - and because testers love pyramids - I created the &lt;strong&gt;Automation Maturity Pyramid&lt;/strong&gt; as a way to measure automation impact.&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%2Fsv7jlnd9evevn4hpqg03.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv7jlnd9evevn4hpqg03.webp" alt=" " width="636" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let's remember why we write automation tests in the first place. At the end-of-the-day, automation tests should support two simple missions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increase product quality &amp;amp; confidence&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accelerate development &amp;amp; deployment&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when we think about the pyramid and its phases, everything we do should ultimately align with those missions.&lt;/p&gt;

&lt;p&gt;The pyramid has four levels of maturity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Confidence&lt;/strong&gt; - Trusting your test results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short-Term Impact&lt;/strong&gt; - Creating value in daily development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed of Development&lt;/strong&gt; - Scaling automation without slowing down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-Term Impact&lt;/strong&gt; - Sustaining trust, visibility, and continuous improvement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each phase builds on the one below it. Later stages only unlock their benefits once the initial foundation is solid. The pyramid is both tool and type agnostic, meaning you can apply it to any automation suite, framework, or testing type that fits your needs.&lt;/p&gt;

&lt;p&gt;Remember, this journey takes time. Think of the pyramid as a compass, not a checklist to rush through. If you're starting fresh, it'll guide you from the beginning. If you already have a suite, it's a framework to measure current impact and decide what to tackle next.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: I talked about this topic in-depth (and with more technical examples) in a conference &lt;a href="https://www.youtube.com/watch?v=202qNHnaZpU&amp;amp;ab_channel=Cypress.io" rel="noopener noreferrer"&gt;here&lt;/a&gt;. If video is more your thing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Phase 1 - Confidence&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A pyramid collapses without a strong base. The same is true with automation. If teams don't trust the test failures (or even successes), everything else becomes meaningless.&lt;/p&gt;

&lt;p&gt;When results are unreliable, people stop acting on them. And when tests are ignored, automation loses its purpose. In many ways, unreliable automation is often worse than not having any at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Tests Must Pass&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Failures will happen. That's not the issue. The danger is when teams normalize broken tests or flaky failures. Every red test should be taken seriously: investigated, understood, and resolved. While there are exceptions, the default culture must be: &lt;strong&gt;stop and fix&lt;/strong&gt;. Adopt the mindset &lt;em&gt;"all tests must pass"&lt;/em&gt;, and technical debt will quickly diminish before it starts. A mature automation test suite starts with an accountable mindset.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What Undermines Confidence&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flakiness&lt;/strong&gt;: Tests that pass or fail inconsistently without code changes. Common causes include race-condition, non-deterministic app behavior, dependent tests or poor test data management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Instability&lt;/strong&gt;: Where you will run your tests matter, especially if multiple options are needed. Can you guarantee tests will run reliably across all environments?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak Data Strategies&lt;/strong&gt;: Do tests always have the data they need? Is it static or dynamic? A strong data strategy reduces countless downstream failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Phase 1 is about &lt;strong&gt;establishing trust&lt;/strong&gt;. Once failures are credible and environments stable, your suite stops being noise and starts being a safety net. A small, confident test suite is more impactful than a large, unstable one. Some actions items to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Research and implement flake-reduction practices for your tool of choice&lt;/li&gt;
&lt;li&gt;Create a culture of accountability: quarantine flaky tests and resolve them quickly&lt;/li&gt;
&lt;li&gt;Write tests environment-agnostically&lt;/li&gt;
&lt;li&gt;Define a consistent test data strategy that works across environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've done these, you're ready for Phase 2.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Phase 2 - Short-Term Impact&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With trust established, the next step is to make automation useful &lt;em&gt;right now&lt;/em&gt;. Tests should provide fast feedback and reduce risk during daily development.&lt;/p&gt;

&lt;p&gt;If tests only run occasionally or if results arrive too late to act on, they don't influence decision-making. The goal is to make automation an indispensable partner for developers, not a background chore.&lt;/p&gt;

&lt;p&gt;This phase is all about defining an initial CI/CD strategy that suites your team's development processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;CI/CD Strategy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A good rule: the closer tests run to code changes, the more valuable they are. Running suites &lt;strong&gt;pre-merge&lt;/strong&gt; ensures failures tie directly to specific commits, not multiple layers of changes. Fewer variables mean quicker triage.&lt;/p&gt;

&lt;p&gt;Nightly or scheduled runs still have a place - especially for full regressions, but the longer the gap between code and results, the harder it is to debug.&lt;/p&gt;

&lt;p&gt;Some common strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-merge Tests&lt;/strong&gt;: Run in under ~10 minutes. Cover critical paths first, then expand with performance in mind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Nightly Regression&lt;/strong&gt;: Capture broader coverage where speed isn't urgent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Tag-Based Gates&lt;/strong&gt;: Sub-groups of tests run based on criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Results Visibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Running tests is meaningless if no one notices the outcomes. Ensure results are clear, fast, and shared.&lt;/p&gt;

&lt;p&gt;Every suite should generate artifacts accessible to all engineers. This includes screenshots, video, error logs and any other additional test information. Without proper artifacts, debugging failures becomes exponentially harder. Additionally, notifications should be immediate and integrated into tools your teams already use.&lt;/p&gt;

&lt;p&gt;A professional rule of mine- act like Veruca Salt from Willy Wonka: &lt;br&gt;
&lt;em&gt;"I want those results and I want them now!"&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Remember, Phase 2 is about &lt;strong&gt;usefulness&lt;/strong&gt;. Once tests deliver fast, actionable feedback, they directly help teams ship better code, quicker. Developers know within minutes when a real-bug is introduced. Testers know when flake is first introduced, for immediate remediation.&lt;/p&gt;

&lt;p&gt;Stick to the mantra: &lt;em&gt;"all tests must pass"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Once you start getting short-term feedback from your tests, it's time to optimize them.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Phase 3 - Speed of Development&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once automation is trusted and embedded in the workflow, the focus shifts to efficiency. The question becomes: how can automation help us move faster without cutting corners?&lt;/p&gt;

&lt;p&gt;At small scale, almost any automation adds value. But as suites grow, inefficiency turns automation into a bottleneck. Tests that take hours to run or are painful to debug become blockers instead of enablers. This phase has three areas of focus: writing, debugging and executing tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Write Tests Faster&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Writing tests faster primarily comes down to test organization and structure. Expanding further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardize Structure&lt;/strong&gt;: Use any pattern that makes sense to you and don't worry about perfection. Any organization beats spaghetti-code chaos. Optimize over-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse Aggressively&lt;/strong&gt;: Create helpers, builders, and shared libraries for scaleability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive Test Planning&lt;/strong&gt;: Review product tickets early to avoid last-minute gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use AI-assisted Tooling&lt;/strong&gt;: Just do it. There's no excuse not to use AI anymore. Embrace our new overlords!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document&lt;/strong&gt;: Look, we all know it sucks…but providing guides and common gotchas reduce ramp-up time as the team grows. What would past you wish they had when they first onboarded?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Debug Tests Faster&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Test failures will happen so response time makes or breaks a suite's value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize Readability&lt;/strong&gt;: Choose clarity over cleverness; smaller, focused tests are easier to diagnose. Always write tests with future you in mind. &lt;em&gt;"Will this make sense to me in six months?".&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce Variables&lt;/strong&gt;: Run tests as close to the change as possible (prioritize pre-merge if not already implemented).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Culture of Accountability:&lt;/strong&gt; Build a habit of immediate triage: treat all fails with the same urgency so at least some resolution occurs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Artifact Tools&lt;/strong&gt;: Interactive runners, browser devtools, and in-depth logs are gold. Improve artifacts as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Run Tests Faster&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This one is simple. How fast do our tests run? Repeat after me: &lt;em&gt;"Nobody brags about a three-hour test suite"&lt;/em&gt;. As the test suite grows, will the team still get quick value without slowing down the process?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallelize&lt;/strong&gt;: Split suites across multiple machines or containers. A must for pre-merge pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subset Tests&lt;/strong&gt;: Run critical paths first; save broader regressions for later. Customize based on need and overall test performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize Code&lt;/strong&gt;: Remove hard-coded waits, reduce unnecessary DOM interactions, apply tool best practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Phase 3 is about &lt;em&gt;efficiency&lt;/em&gt;. Automation should accelerate delivery, not drag it down. When done well, it enables rapid iteration and frequent, confident releases. All of a sudden our monthly releases can now be reduced to weekly. Then daily. Then maybe even multiple times a day, if you're feeling extra daring. All thanks to your automation test suite.&lt;/p&gt;

&lt;p&gt;You deserve a raise.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Phase 4 - Long-Term Impact&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The final phase is about &lt;em&gt;sustainability&lt;/em&gt;. Once automation is fast, useful, and trusted, it must also deliver long-term value.&lt;/p&gt;

&lt;p&gt;Teams and products evolve. Without continuous investment, automation rots: tests get flaky, results get ignored, and the pyramid crumbles. Which is all super sad. Professional advice, don't be sad.&lt;/p&gt;

&lt;p&gt;Long-term impact ensures automation remains a source of truth while showcasing just how cool your team is.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Metrics Inform, Not Punish&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This phase is purely about responding to metrics, but use them wisely. Metrics should guide investment, not assign blame. Focus on impactful metrics that guide your automation roadmap. Simply, you don't know what to improve if you don't know what's ineffective.&lt;/p&gt;

&lt;p&gt;Some Suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Coverage&lt;/strong&gt;: Directional, not definitive. Pair with quality checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass/fail and flake rates&lt;/strong&gt;: Indicators of credibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution time&lt;/strong&gt;: Is the suite scaling with the team?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-to-resolution (TTR)&lt;/strong&gt;: How quickly do teams fix failures?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defect detection efficiency (DDE)&lt;/strong&gt;: Percentage of bugs caught by automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If possible, consider augmenting these with a dashboard where visibility is further increased. Visual trends make it easier to consume historical trends and identify weaknesses. Plus bar graphs are fun and line graphs always look convincing. Don't even threaten me with a good time and bring up pie charts.&lt;/p&gt;

&lt;p&gt;This phase is small but important. It's the culmination of all the previous phases, and purely intended to bring visibility into how well things went in the previous phases. It drives future revisions and ensures the test suite is never stagnant in it's impact.&lt;/p&gt;

&lt;p&gt;Phase 4 is all about &lt;strong&gt;trust at scale&lt;/strong&gt;. Mature automation creates transparency, informs investment, and continues to improve over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Putting It All Together&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The Automation Maturity Pyramid is a lot smaller than the Pyramids of Giza but much more relatable since those are real and in Egypt and this is thought-leadership and about testing. Just to clarify any confusion to this point.&lt;/p&gt;

&lt;p&gt;But seriously, it's about measuring your impact, one phase at a time. Building a successful automation test suite is hard without proper guidance. There's many technical steps and failures can quickly become overwhelming and frustrating.&lt;/p&gt;

&lt;p&gt;To recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confidence Firs&lt;/strong&gt;t: You have to trust your tests, always. The rest will follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early Wins&lt;/strong&gt;: No matter the test suite size, obtain value. Start catching real issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take small steps&lt;/strong&gt;: Steady improvements compound into big gains. Efficiency is a learning curve and only obtained through experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Welcome Failures&lt;/strong&gt;: &lt;em&gt;Hello failures, come on it. Have a seat. Let's talk about how you're making my current life bad so we can make my future life good.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celebrate Progress&lt;/strong&gt;: Building a reliable, impactful suite is a team achievement. Be proud of that green test run, those first 100 tests, or the first real-bug your suite caught. You're a rockstar, genuinely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Done well, automation isn't overhead - it's a strategic advantage. Build a base of trust, create fast feedback loops, optimize for speed, and commit to long-term transparency. That's how you turn test automation into a driver of product success.&lt;/p&gt;

&lt;p&gt;Best of luck in your climb. And as always, happy testing.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>cypress</category>
      <category>automation</category>
    </item>
    <item>
      <title>Cypress — How to Create Automatic Weekly Flake Alerting</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Fri, 15 Aug 2025 14:18:45 +0000</pubDate>
      <link>https://dev.to/cydavid/cypress-how-to-create-automatic-weekly-flake-alerting-90i</link>
      <guid>https://dev.to/cydavid/cypress-how-to-create-automatic-weekly-flake-alerting-90i</guid>
      <description>&lt;p&gt;Flaky tests waste time, erode confidence, and make debugging a nightmare — especially when running in parallel across multiple CI machines.&lt;/p&gt;

&lt;p&gt;Cypress Cloud provides fantastic flake detection and historical insights, but what if you don’t have access to it? You’re not out of luck.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll show you how to build your own &lt;strong&gt;automated flake alerting system&lt;/strong&gt; that runs weekly, requires no manual effort, and integrates with your existing workflow.&lt;/p&gt;

&lt;p&gt;By the end, you’ll have a setup that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracks flake across different environments&lt;/li&gt;
&lt;li&gt;Detects trends over time&lt;/li&gt;
&lt;li&gt;Automatically alerts your team when thresholds are exceeded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive in.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Prerequisite: Merge Report Infrastructure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This tutorial builds upon an earlier post &lt;a href="https://dev.to/cydavid/cypress-how-to-create-a-merge-report-in-your-pipeline-pa8"&gt;How to Create a Custom Merge Report in Cypress&lt;/a&gt;. If you haven’t implemented that yet, start there — we’ll extend that implementation to save, merge and analyze flake data.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;1. Save the Flake Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once your custom merge-report script is running, let’s expand it to track flake history over time&lt;/p&gt;

&lt;p&gt;Add this at the bottom of your merge script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Flake Detection&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakeHistoryPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flake-history.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakeHistory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: Differentiate by environment&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&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;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;

  &lt;span class="k"&gt;for &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;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;flakyTests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;flakeHistory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastSeen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Save file&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flakeHistoryPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flakeHistory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Flake history created at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;flakeHistoryPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Tip: Tracking the env (like dev or local) helps you isolate which environments are producing flaky results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now each test run will produce a &lt;code&gt;flake-history.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Let’s persist it for future aggregation.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;2. Add a Weekly Aggregator Pipeline&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We’ll now persist the flake history files and process them weekly.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2.1 Persist Flake Files&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In your merge workflow, upload the artifact after the script runs. The following example uses Github Actions but any CI/CD tool works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Cypress Flake Results&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flake-history-${{ github.run_id }}&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress/results/flake-history.json&lt;/span&gt;
    &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;2.2 Weekly Aggregation Workflow&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Next, set up a new pipeline workflow that runs weekly. It will fetch all flake files from our recent runs and prepare them for merging&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cypress - Flake Aggregator&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MON'&lt;/span&gt; &lt;span class="c1"&gt;# 3am MDT every Monday, tweak as desired&lt;/span&gt;
&lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;aggregate-flake-files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-runner&lt;/span&gt; &lt;span class="c1"&gt;# Your runner&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

  &lt;span class="c1"&gt;# 1. Get Workflow Run IDs from past 7 days&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get Recent Test Workflow Runs&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-cypress-runs&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;const workflowNames = ['Cypress Tests'] // Your Cypress GH Workflow names&lt;/span&gt;
            &lt;span class="s"&gt;const sinceDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // 7 days ago&lt;/span&gt;
            &lt;span class="s"&gt;const allRunIds = []&lt;/span&gt;

            &lt;span class="s"&gt;const workflowsResp = await github.rest.actions.listRepoWorkflows({&lt;/span&gt;
              &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
              &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
            &lt;span class="s"&gt;})&lt;/span&gt;

            &lt;span class="s"&gt;for (const name of workflowNames) {&lt;/span&gt;
              &lt;span class="s"&gt;const wf = workflowsResp.data.workflows.find(wf =&amp;gt; wf.name === name)&lt;/span&gt;
              &lt;span class="s"&gt;if (!wf) continue&lt;/span&gt;

              &lt;span class="s"&gt;let page = 1&lt;/span&gt;
              &lt;span class="s"&gt;let keepFetching = true&lt;/span&gt;

              &lt;span class="s"&gt;while (keepFetching) {&lt;/span&gt;
                &lt;span class="s"&gt;const runsResp = await github.rest.actions.listWorkflowRuns({&lt;/span&gt;
                  &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
                  &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
                  &lt;span class="s"&gt;workflow_id: wf.id,&lt;/span&gt;
                  &lt;span class="s"&gt;per_page: 100,&lt;/span&gt;
                  &lt;span class="s"&gt;page,&lt;/span&gt;
                &lt;span class="s"&gt;})&lt;/span&gt;

                &lt;span class="s"&gt;// Fetch all workflows within the last week and persist run ids&lt;/span&gt;
                &lt;span class="s"&gt;const recentRuns = runsResp.data.workflow_runs.filter(run =&amp;gt;&lt;/span&gt;
                  &lt;span class="s"&gt;new Date(run.created_at) &amp;gt;= sinceDate&lt;/span&gt;
                &lt;span class="s"&gt;)&lt;/span&gt;

                &lt;span class="s"&gt;recentRuns.forEach(run =&amp;gt; allRunIds.push(run.id))&lt;/span&gt;

                &lt;span class="s"&gt;// Stop if there are no more recent runs or we hit end of pages&lt;/span&gt;
                &lt;span class="s"&gt;if (&lt;/span&gt;
                  &lt;span class="s"&gt;recentRuns.length &amp;lt; runsResp.data.workflow_runs.length ||&lt;/span&gt;
                  &lt;span class="s"&gt;runsResp.data.workflow_runs.length === 0&lt;/span&gt;
                &lt;span class="s"&gt;) {&lt;/span&gt;
                  &lt;span class="s"&gt;keepFetching = false&lt;/span&gt;
                &lt;span class="s"&gt;} else {&lt;/span&gt;
                  &lt;span class="s"&gt;page++&lt;/span&gt;
                &lt;span class="s"&gt;}&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;return allRunIds&lt;/span&gt;
          &lt;span class="na"&gt;result-encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Download Artifacts&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Flake Artifacts&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;mkdir -p cypress/flakes // Wherever you stored the results&lt;/span&gt;
        &lt;span class="s"&gt;echo '[${{ steps.get-cypress-runs.outputs.result }}]' | jq -r '.[]' | while read RUN_ID; do&lt;/span&gt;
          &lt;span class="s"&gt;artifact_name="flake-history-$RUN_ID"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Attempting to download $artifact_name from run $RUN_ID"&lt;/span&gt;
          &lt;span class="s"&gt;if gh run download $RUN_ID -n $artifact_name -D cypress/flakes/; then&lt;/span&gt;
            &lt;span class="s"&gt;// Rename each file so it's unique&lt;/span&gt;
            &lt;span class="s"&gt;mv "cypress/flakes/flake-history.json" "cypress/flakes/flake-history-$RUN_ID.json"&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;echo "Missing: $artifact_name"&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
        &lt;span class="s"&gt;done&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Merge All Flake Files&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge Flake History Files&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node cypress/merge-flake-history.js&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Optionally Upload Aggregated Report&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Aggregated Report&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aggregated-flake-history&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress/merged-flake-history.json&lt;/span&gt;
        &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, every Monday this job collects all flake history files from the past week and merges them.&lt;/p&gt;

&lt;p&gt;Here’s the complete process at a glance, from Cypress test runs to automated alerts:&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%2Fq68n0nwxd8xeysc4xiy1.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%2Fq68n0nwxd8xeysc4xiy1.png" alt=" " width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;3. Merge the Flake Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With the workflow in place, create the new &lt;code&gt;merge-flake-history.js&lt;/code&gt; script. This script will loop through all the flake files from the past week and aggregate counts by test name and environment.&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="cm"&gt;/* eslint-disable no-undef */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedFlakeHistoryPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merged-flake-history.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergeFlakeHistories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;flakesDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakeFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flakesDir&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;f&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;flakeFiles&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="s2"&gt; flake files to analyze`&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;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="c1"&gt;// Loop through all flake files&lt;/span&gt;
  &lt;span class="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;flakeFiles&lt;/span&gt;&lt;span class="p"&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;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;flakesDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// For each flaky test, add to a new merged object &lt;/span&gt;
    &lt;span class="k"&gt;for &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;testName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;flakeData&lt;/span&gt;&lt;span class="p"&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;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastSeen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flakeData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&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;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;]&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="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;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;lastSeen&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Count amount of times the test has flaked across all files&lt;/span&gt;
        &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingDate&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;lastSeen&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;newDate&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastSeen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Use the earliest lastSeen timestamp&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;newDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;existingDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;lastSeen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastSeen&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;// Persist merge flake report&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedFlakeHistoryPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Merged flake history written to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mergedFlakeHistoryPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;mergeFlakeHistories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Checkout Flow - should display cart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lastSeen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-05T14:58:03.908Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Login - valid credentials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lastSeen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-05T14:58:03.913Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"lastSeen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-07T12:40:06.156Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;4. Alert When Thresholds are Exceeded&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, with our merged report, we can trigger alerts when flake counts pass defined thresholds.&lt;/p&gt;

&lt;p&gt;Here’s an example using an integration using Linear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define thresholds based on env&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LOCAL_FLAKE_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEV_FLAKE_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="c1"&gt;// Any integration secrets&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LINEAR_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LINEAR_API_KEY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LINEAR_TEAM_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LINEAR_TEAM_ID&lt;/span&gt;

&lt;span class="c1"&gt;// Linear integration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createLinearTicket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastSeen&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      mutation {
        issueCreate(input: {
          title: "Cypress Flaky Test: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;",
          description: "This test flaked &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times in the last week on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. First observed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastSeen&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;",
          teamId: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LINEAR_TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
        }) {
          success
        }
      }
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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.linear.app/graphql&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;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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LINEAR_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issueCreate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Linear ticket created for "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testName&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to create Linear ticket for "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testName&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;json&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error creating Linear ticket for "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testName&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;err&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;// After merging results in mergeFlakeHistories &lt;/span&gt;
&lt;span class="k"&gt;for &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;testName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;env&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&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;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastSeen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// Optionally evaluate threshold based on env&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;LOCAL_FLAKE_THRESHOLD&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DEV_FLAKE_THRESHOLD&lt;/span&gt;

    &lt;span class="c1"&gt;// Create Linear ticket if flake detected&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;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Flake threshold exceeded: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testName&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;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createLinearTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastSeen&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;blockquote&gt;
&lt;p&gt;💡 Tip: Swap out Linear for Jira, Slack, or any other alerting tool your team uses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simple, example Linear ticket that’s automatically created might look 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%2Fmh4dgbynkimrlpse1kb7.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%2Fmh4dgbynkimrlpse1kb7.png" alt=" " width="720" height="199"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With this setup, &lt;strong&gt;automatically every week&lt;/strong&gt; your team gets actionable alerts about the flakiest tests — without anyone manually digging through logs.&lt;/p&gt;

&lt;p&gt;You’ll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically collect and merge flake data&lt;/li&gt;
&lt;li&gt;Detect patterns per environment&lt;/li&gt;
&lt;li&gt;Alert only when thresholds are breached&lt;/li&gt;
&lt;li&gt;Keep your test suite healthier over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need a full observability platform to track flake — just a clever CI workflow and a few scripts. For teams needing more depth, Cypress Cloud offers free trials.&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Cypress - Real-Time Multi-User Testing Made Easy</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Mon, 02 Jun 2025 14:00:41 +0000</pubDate>
      <link>https://dev.to/cydavid/cypress-real-time-multi-user-testing-made-easy-1012</link>
      <guid>https://dev.to/cydavid/cypress-real-time-multi-user-testing-made-easy-1012</guid>
      <description>&lt;p&gt;When building applications with real-time features or collaborative workflows, you'll often encounter scenarios where actions by one user must trigger updates for another. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing a chat app where a message from one user should instantly appear for another&lt;/li&gt;
&lt;li&gt;Verifying permission boundaries in admin/user portals&lt;/li&gt;
&lt;li&gt;Validating notifications or assignments triggered by another account&lt;/li&gt;
&lt;li&gt;Testing multiplayer games where player actions affect others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditionally, automating these scenarios can seem daunting - you might imagine needing multiple browser tabs, separate user sessions, or even multiple parallel test runners. However, &lt;a href="https://docs.cypress.io/app/references/trade-offs" rel="noopener noreferrer"&gt;Cypress was designed&lt;/a&gt; with a single-browser, single-tab architecture, meaning it doesn't natively support true multi-tab or multi-window testing. Fortunately, there's a much simpler and more reliable way: by leveraging Cypress's API commands, you can simulate multi-user behavior efficiently, all within a single test.&lt;/p&gt;

&lt;p&gt;Let's break down how this works using a simple, chat application scenario: we have two users and want to verify that when User 2 sends a message, User 1 receives it instantly. The pattern follows three main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate both users and store sessions&lt;/li&gt;
&lt;li&gt;Track the active API request user&lt;/li&gt;
&lt;li&gt;Dynamically set API request headers for any API request&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Authenticate Both Users and Store Sessions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The foundation of multi-user testing is the ability to switch between users on demand. To do this, you'll first need to authenticate all relevant users up front and securely store their authentication details for later use.&lt;/p&gt;

&lt;p&gt;Below is an example login custom command.  It logs in as a specific user and saves the necessary authentication cookies. It utilizes &lt;a href="https://docs.cypress.io/api/commands/session" rel="noopener noreferrer"&gt;cy.session&lt;/a&gt; command for session persistence across tests (this is optional but highly recommended for a performative test suite long-term). &lt;br&gt;
Additionally, this setup uses the &lt;a href="https://github.com/NoriSte/cypress-wait-until" rel="noopener noreferrer"&gt;cypress-wait-until&lt;/a&gt; plugin to extend dynamic waiting on the &lt;a href="https://docs.cypress.io/api/commands/getcookie" rel="noopener noreferrer"&gt;cy.getCookie&lt;/a&gt; command which natively doesn't retry. This is also optional but without, it's possible the &lt;code&gt;cy.getCookie&lt;/code&gt; command will be performed before the application has created all the appropriate tokens.&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress-wait-until&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// cy.loginViaAuth0UI custom command&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;loginViaAuth0UI&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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginUsers&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;Cypress&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Logging in as &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&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="c1"&gt;// Set initial value if undefined&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userSessionCookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userSessionCookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; 

  &lt;span class="c1"&gt;// This variable will indicate if we are creating or restoring a session&lt;/span&gt;
  &lt;span class="c1"&gt;// Either way we will be able to store off the current user's cookies&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;initialSessionToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

  &lt;span class="c1"&gt;// Your user credentials&lt;/span&gt;
  &lt;span class="c1"&gt;// Here we assume they are stored in cypress.config.js as an env var&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USERS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;user&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;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&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;// App landing page redirects to Auth0.&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace with your actual Auth0 or authentication provider URL&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;origin&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://your-auth0-url/&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;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;input#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="nx"&gt;username&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;input#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="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Continue&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;span class="c1"&gt;// Ensure we are redirected to the homepage after login&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&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;include&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="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HOMEPAGE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; 

    &lt;span class="c1"&gt;// After initial session creation, store cookies&lt;/span&gt;
    &lt;span class="nf"&gt;waitUntilTokensExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;initialSessionToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;initialSessionToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// If restoring a session, store off restored cookies&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;initialSessionToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;waitUntilTokensExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Switch the current user to be the api request user&lt;/span&gt;
  &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiRequestUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&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'll notice that whether a brand-new session or existing session is restored, the login commands &lt;code&gt;waitUntilTokensExist&lt;/code&gt;. Let's check out this helper below&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addTokenIfNotExists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginUsers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;tokenStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userSessionCookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Add our cookies to the Cypress.env('userSessionCookies') env var&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;tokenStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tokenStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&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;cookies&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;// Helper function that waits until cookies are populated for the current user&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitUntilTokensExist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginUsers&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;// Utilizes the `cypress-wait-until' plugin &lt;/span&gt;
  &lt;span class="c1"&gt;// This guarantees the authenticated cookies exist before storing them&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&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;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="nx"&gt;_000&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="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;getAllCookies&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;cookies&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;// Save off cookies if not already added&lt;/span&gt;
      &lt;span class="nf"&gt;addTokenIfNotExists&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;waitUntilTokenExists&lt;/code&gt; waits until Cypress detects a key authentication cookie (&lt;code&gt;XSRF-TOKEN&lt;/code&gt;, for example) after login. Only then does it grab all cookies for the user and save them. This avoids race conditions where Cypress would try to use cookies before they're ready.&lt;/p&gt;

&lt;p&gt;Finally, it saves off each user's authentication cookies to a shared storage (&lt;code&gt;userSessionCookies&lt;/code&gt;) with a final &lt;code&gt;addTokenIfNotExist&lt;/code&gt; helper. By indexing cookies by user, you ensure each user's credentials are available whenever you need to act as that user.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Track the Active API Request User&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once you have multiple users authenticated, you need a mechanism to decide which user is "sending" an API request at any given time. This is accomplished by keeping track of the "active" API request user via an environment variable (like &lt;code&gt;Cypress.env('apiRequestUser')&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;By creating a simple custom command, &lt;code&gt;cy.switchAPIRequestUser()&lt;/code&gt;, which just updates the &lt;code&gt;apiRequestUser&lt;/code&gt; variable, switching between users is trivial. Additionally, since this is a &lt;a href="https://docs.cypress.io/app/core-concepts/introduction-to-cypress#Chains-of-Commands" rel="noopener noreferrer"&gt;custom command&lt;/a&gt;, it's accessible throughout all our specs and tests.&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;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;switchAPIRequestUser&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;user&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;Cypress&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`switchAPIRequestUser: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&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;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiRequestUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&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;
  
  
  &lt;strong&gt;Step 3: Dynamically Set API Request Headers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Lastly, now that our user cookies are stored, whenever we need to send a authenticated API request (e.g., simulating User 2 sending a message), we need to set up each API request with the right authentication headers. These two helpers do the heavy lifting:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getAuthenticationHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Retrieve the XSRF token and cookies from the environment variables&lt;/span&gt;
  &lt;span class="c1"&gt;// Uses the current request user to get the correct tokens&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;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userSessionCookies&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="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiRequestUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="c1"&gt;// Pull key authentication tokens from cookies&lt;/span&gt;
  &lt;span class="c1"&gt;// This might change based on your application's authentication mechanism&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SESSION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;

  &lt;span class="c1"&gt;// Example of setting authentication headers&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SESSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`XSRF-TOKEN=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; SESSION=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; JSESSIONID=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function fetches the cookies for the &lt;em&gt;currently selected&lt;/em&gt; user (from Step 2) and formats them as headers for an authenticated API request. It abstracts away the manual management of tokens for every request.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;RequestOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Chainable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getAuthenticationHeaders&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// Set authentication headers using the current api request user&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;headers&lt;/span&gt;&lt;span class="p"&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="s2"&gt;`your base api url/&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;url&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="c1"&gt;// Replace with your actual base API URL&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, this is a wrapper around &lt;code&gt;cy.request&lt;/code&gt; that automatically injects the correct authentication headers. Now, every API call you make can easily impersonate whichever user you've selected with &lt;code&gt;cy.switchAPIRequestUser&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Simulate Multi-User Interactions in Your Test&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here's how it all fits together in a real-world scenario - simulating two users in a chat app test:&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;should receive a real-time message from another user&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;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/notifications/incoming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interceptGETIncomingMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Authenticate with BOTH users first&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loginViaAuth0UI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LoginUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user2&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;loginViaAuth0UI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LoginUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user1&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;// Your app url&lt;/span&gt;

  &lt;span class="c1"&gt;// Stay logged in as user1 but switch the API request user to user2&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchAPIRequestUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LoginUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Send a message using the API from user2&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Wait on some received API call from user1's perspective&lt;/span&gt;
    &lt;span class="c1"&gt;// Optional based on your application&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@interceptGETIncomingMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate as user1 we get a real-time incoming message&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;.message-box&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="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test message&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;Remember these steps: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate and store both users using your login helper.&lt;/li&gt;
&lt;li&gt;Visit your app as User 1 (the main UI context).&lt;/li&gt;
&lt;li&gt;Switch API context to User 2 and perform some action via the API.&lt;/li&gt;
&lt;li&gt;Wait for the real-time message event and validate it appears for User 1 in the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using this pattern, you can reliably test real-time, cross-user features - all within one Cypress runner.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Multi-user end-to-end testing in Cypress doesn't have to be complicated. By authenticating all necessary users up front, securely storing their session tokens, and dynamically switching API request context, you can confidently validate real-time and collaborative features.&lt;/p&gt;

&lt;p&gt;This approach is faster, more maintainable, and less brittle than juggling multiple browser sessions or tabs. It can easily be extended to more than two users or more complex workflows.&lt;/p&gt;

&lt;p&gt;If you're building applications where user-to-user interactions matter, this pattern belongs in your Cypress toolkit.&lt;/p&gt;

&lt;p&gt;As always, happy testing!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>automation</category>
      <category>multiuser</category>
      <category>testing</category>
    </item>
    <item>
      <title>Cypress — How to Create a Merge Report in your Pipeline</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Tue, 15 Apr 2025 21:46:59 +0000</pubDate>
      <link>https://dev.to/cypress/cypress-how-to-create-a-merge-report-in-your-pipeline-3c0j</link>
      <guid>https://dev.to/cypress/cypress-how-to-create-a-merge-report-in-your-pipeline-3c0j</guid>
      <description>&lt;p&gt;Cypress Cloud is a powerful tool for tracking test health and identifying long-term patterns. However, for teams without the financial bandwidth to use Cypress Cloud, having access to similar analytics — such as flake percentage, test performance, and failure data — remains essential, especially as test suites grow in size and complexity.&lt;/p&gt;

&lt;p&gt;Combine that with a test suite running in parallel, and tracking health across multiple runners can quickly become a time-consuming chore.&lt;/p&gt;

&lt;p&gt;In this blog, I’ll break down an easy pattern you can implement to generate a &lt;strong&gt;JSON merge report&lt;/strong&gt;, providing a single place to track test health and custom analytics.&lt;/p&gt;

&lt;p&gt;This tutorial assumes your test suite runs in &lt;strong&gt;parallel&lt;/strong&gt;. While it will still work for a single-runner setup, the benefits are magnified when test results are spread across jobs. If you need parallelization, check out the free &lt;a href="https://github.com/bahmutov/cypress-split" rel="noopener noreferrer"&gt;cypress-split&lt;/a&gt; plugin.&lt;/p&gt;

&lt;p&gt;To create the test report, follow these four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save the test results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Merge the test results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the script to the pipeline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the report&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Save the Test Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To begin, we need to save the results of each individual test run. This can be achieved using the after:run event.&lt;/p&gt;

&lt;p&gt;Here’s an example of how to capture and save results, with each step commented along the 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="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after:run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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;// 1. Define results directory&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress/results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Create the results directory, if it doesn't exist&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Write the results to a file &lt;/span&gt;
    &lt;span class="c1"&gt;// Each parallel runner need its own results file. &lt;/span&gt;
    &lt;span class="c1"&gt;// Use randomUUID to ensure uniqueness&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`test-results-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Write the result to the results directory&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each parallel runner will now save its own test result file to a shared results folder.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Merge the Test Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, let’s combine those individual files into a single report. The script below will read all result files, analyze them, and generate a final merged report.&lt;/p&gt;

&lt;p&gt;Create a new file: &lt;code&gt;merge-results.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// 1. Define the results directory and the merged results file path&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedResultsFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merged-results.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create your custom result format JSON&lt;/span&gt;
&lt;span class="c1"&gt;// Below is an example of example metrics, you can customize it to suite your needs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overallResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;totalDuration&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="na"&gt;totalTests&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="na"&gt;totalPassed&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="na"&gt;totalFailed&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="na"&gt;totalPending&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="na"&gt;totalSkipped&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="na"&gt;flakePercentage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakyTests&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;slowestTests&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;errors&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;mergedResults&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="nx"&gt;overallResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;flakyTests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slowestTests&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Helper methods &lt;/span&gt;
&lt;span class="c1"&gt;// Mine convert milliseconds to minutes for better readability&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertMSToSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&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;ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertSecondsToMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&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;convertMSToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Core Method&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergeResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Check if the results directory exists, exit if not&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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 results directory 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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&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;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merged-results.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Loop through and read each individual result file&lt;/span&gt;
  &lt;span class="c1"&gt;// Save off the results into a JSON object&lt;/span&gt;
  &lt;span class="nx"&gt;resultFiles&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;file&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Loop through each test run and extract the relevant metrics&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&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;run&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;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tests&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;test&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;// Flaky Tests&lt;/span&gt;
        &lt;span class="c1"&gt;// A test is considered flaky if it has multiple attempts and at least one of them passed&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;attempt&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;attempt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passed&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="nx"&gt;flakyTests&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="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Errors&lt;/span&gt;
        &lt;span class="c1"&gt;// A test is considered to have failed if its state is 'failed' and it has a displayError&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayError&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;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;error&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;// Slowest Tests&lt;/span&gt;
        &lt;span class="c1"&gt;// Save off all test regardless of their state&lt;/span&gt;
        &lt;span class="nx"&gt;slowestTests&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="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;convertMSToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&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;// Overall Results&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPassed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPassed&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalFailed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalFailed&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPending&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPending&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalSkipped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalSkipped&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// 7. Now that all files have been processed, we can calculate overall metrics&lt;/span&gt;
  &lt;span class="c1"&gt;// Convert total duration into a readable format&lt;/span&gt;
  &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;convertSecondsToMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; min`&lt;/span&gt;
  &lt;span class="c1"&gt;// Sort the slowest tests by duration&lt;/span&gt;
  &lt;span class="nx"&gt;slowestTests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a overall flake percentage for the run&lt;/span&gt;
  &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flakePercentage&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="nx"&gt;flakyTests&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="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 8. Save the merged results to a file&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResultsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`✅ Merged test results saved at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mergedResultsFilePath&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="c1"&gt;// 9. Call the mergeResults function to execute the script&lt;/span&gt;
&lt;span class="nf"&gt;mergeResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Add the Script to the Pipeline&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that the &lt;code&gt;merge-results.js&lt;/code&gt; script is created, let’s utilize it in the CI/CD pipeline. Add a new merge job and ensure it runs after all Cypress run jobs have completed. Below is an example using GitHub Actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="c1"&gt;# Run Cypress Test Job&lt;/span&gt;
  &lt;span class="na"&gt;cypress-run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-runner&lt;/span&gt; &lt;span class="c1"&gt;# Your runner&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Number of parallel jobs, uses cypress-split&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# 1. Run Cypress&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Cypress Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run cy:run&lt;/span&gt; &lt;span class="c1"&gt;# Your run command&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SPLIT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ strategy.job-total }}&lt;/span&gt;
          &lt;span class="na"&gt;SPLIT_INDEX&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ strategy.job-index }}&lt;/span&gt; 
      &lt;span class="c1"&gt;# 2. Upload Cypress Results&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Cypress Results&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success() || failure()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress-results-${{ strategy.job-index }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results&lt;/span&gt; &lt;span class="c1"&gt;# Your results folder path&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

  &lt;span class="c1"&gt;# Merge Cypress Test Results Job&lt;/span&gt;
  &lt;span class="na"&gt;merge-test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-runner&lt;/span&gt; &lt;span class="c1"&gt;# Your runner&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cypress-run&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Ensure this job runs after all cypress run jobs have finished&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success() || failure()&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# 3. Download Cypress Results, since they're saved from the previous job&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Cypress Results&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress-results-*&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results&lt;/span&gt; &lt;span class="c1"&gt;# Your results folder path&lt;/span&gt;
          &lt;span class="na"&gt;merge-multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="c1"&gt;# 4. Merge Cypress Results - Call the merge script&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge Test Results&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node your-folder/cypress/merge-results.js&lt;/span&gt; &lt;span class="c1"&gt;# Your merge script path&lt;/span&gt;
      &lt;span class="c1"&gt;# 5. Upload Merged Results &lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Merged Report&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;overwrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results/merged-results.json&lt;/span&gt; &lt;span class="c1"&gt;# Your merged results output file&lt;/span&gt;
          &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The high-level job structure should look something 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%2Fx1mvuapz2jjqsw5jhidt.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%2Fx1mvuapz2jjqsw5jhidt.png" alt="Image description" width="720" height="262"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Use the Report&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each time your pipeline runs, it will now generate a merged JSON file as a pipeline artifact to be downloaded. Here’s a sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;merged-results.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalDuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"45.21 min"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalPassed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalFailed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalPending"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalSkipped"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flakePercentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flakyTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 1 - should create a new user"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 12 - should delete an existing user"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slowestTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 1 - Test Number 14 - should edit an existing user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"214.14"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 3 - Test Number 2 - should paginate the user list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"174.29"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 21 - Test Number 3 - should submit a new bill for an existing user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AssertionError: Timed out retrying after 4000ms: expected '27251' to equal '27,251'"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 13 -  should invoice a user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CypressError: Timed out retrying after 7500ms: `cy.wait()` timed out waiting `7500ms` for the 1st request to the route: `interceptMyUser`."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this data in a single file, your team can quickly identify flaky tests, bottlenecks, and failures without digging through multiple CI job logs. This report is easy to share and provides direct and clean insight making it crucial as part of the CI/CD troubleshooting process.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By generating your own test analytics, you unlock insight into test reliability and performance — without relying on third-party tools. This kind of visibility is essential to keeping a large test suite healthy and maintainable.&lt;/p&gt;

&lt;p&gt;Now that this initial data is available, for future iterations one could consider expanding the logic to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate alerts for flake thresholds or long runtimes&lt;/li&gt;
&lt;li&gt;Track results across multiple runs and upload into an internal dashboard&lt;/li&gt;
&lt;li&gt;Identify differences between previous result health to spot immediate regressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need a full observability platform to spot trends — just a clever script and a bit of CI magic. For those looking for a bit more or want to experience it's value, &lt;a href="https://go.cypress.io/cloud-free-trial?utm_source=blog&amp;amp;utm_medium=dev_to&amp;amp;utm_campaign=cypress-cloud&amp;amp;utm_content=reporting" rel="noopener noreferrer"&gt;Cypress Cloud offers free trials&lt;/a&gt;. &lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Cypress — How to Create a Merge Report in your Pipeline</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Tue, 15 Apr 2025 21:46:59 +0000</pubDate>
      <link>https://dev.to/cydavid/cypress-how-to-create-a-merge-report-in-your-pipeline-pa8</link>
      <guid>https://dev.to/cydavid/cypress-how-to-create-a-merge-report-in-your-pipeline-pa8</guid>
      <description>&lt;p&gt;Cypress Cloud is a powerful tool for tracking test health and identifying long-term patterns. However, for teams without the financial bandwidth to use Cypress Cloud, having access to similar analytics — such as flake percentage, test performance, and failure data — remains essential, especially as test suites grow in size and complexity.&lt;/p&gt;

&lt;p&gt;Combine that with a test suite running in parallel, and tracking health across multiple runners can quickly become a time-consuming chore.&lt;/p&gt;

&lt;p&gt;In this blog, I’ll break down an easy pattern you can implement to generate a &lt;strong&gt;JSON merge report&lt;/strong&gt;, providing a single place to track test health and custom analytics.&lt;/p&gt;

&lt;p&gt;This tutorial assumes your test suite runs in &lt;strong&gt;parallel&lt;/strong&gt;. While it will still work for a single-runner setup, the benefits are magnified when test results are spread across jobs. If you need parallelization, check out the free &lt;a href="https://github.com/bahmutov/cypress-split" rel="noopener noreferrer"&gt;cypress-split&lt;/a&gt; plugin.&lt;/p&gt;

&lt;p&gt;To create the test report, follow these four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save the test results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Merge the test results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the script to the pipeline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the report&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Save the Test Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To begin, we need to save the results of each individual test run. This can be achieved using the after:run event.&lt;/p&gt;

&lt;p&gt;Here’s an example of how to capture and save results, with each step commented along the 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="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after:run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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;// 1. Define results directory&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress/results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Create the results directory, if it doesn't exist&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Write the results to a file &lt;/span&gt;
    &lt;span class="c1"&gt;// Each parallel runner need its own results file. &lt;/span&gt;
    &lt;span class="c1"&gt;// Use randomUUID to ensure uniqueness&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`test-results-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Write the result to the results directory&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each parallel runner will now save its own test result file to a shared results folder.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Merge the Test Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, let’s combine those individual files into a single report. The script below will read all result files, analyze them, and generate a final merged report.&lt;/p&gt;

&lt;p&gt;Create a new file: &lt;code&gt;merge-results.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// 1. Define the results directory and the merged results file path&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedResultsFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merged-results.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create your custom result format JSON&lt;/span&gt;
&lt;span class="c1"&gt;// Below is an example of example metrics, you can customize it to suite your needs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overallResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;totalDuration&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="na"&gt;totalTests&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="na"&gt;totalPassed&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="na"&gt;totalFailed&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="na"&gt;totalPending&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="na"&gt;totalSkipped&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="na"&gt;flakePercentage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flakyTests&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;slowestTests&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;errors&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;mergedResults&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="nx"&gt;overallResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;flakyTests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slowestTests&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Helper methods &lt;/span&gt;
&lt;span class="c1"&gt;// Mine convert milliseconds to minutes for better readability&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertMSToSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&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;ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertSecondsToMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&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;convertMSToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Core Method&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergeResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Check if the results directory exists, exit if not&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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 results directory 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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&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;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merged-results.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Loop through and read each individual result file&lt;/span&gt;
  &lt;span class="c1"&gt;// Save off the results into a JSON object&lt;/span&gt;
  &lt;span class="nx"&gt;resultFiles&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;file&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Loop through each test run and extract the relevant metrics&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&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;run&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;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tests&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;test&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;// Flaky Tests&lt;/span&gt;
        &lt;span class="c1"&gt;// A test is considered flaky if it has multiple attempts and at least one of them passed&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;attempt&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;attempt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passed&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="nx"&gt;flakyTests&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="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Errors&lt;/span&gt;
        &lt;span class="c1"&gt;// A test is considered to have failed if its state is 'failed' and it has a displayError&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayError&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;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;error&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;// Slowest Tests&lt;/span&gt;
        &lt;span class="c1"&gt;// Save off all test regardless of their state&lt;/span&gt;
        &lt;span class="nx"&gt;slowestTests&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="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;convertMSToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&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;// Overall Results&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPassed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPassed&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalFailed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalFailed&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPending&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPending&lt;/span&gt;
    &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalSkipped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalSkipped&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// 7. Now that all files have been processed, we can calculate overall metrics&lt;/span&gt;
  &lt;span class="c1"&gt;// Convert total duration into a readable format&lt;/span&gt;
  &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;convertSecondsToMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDuration&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; min`&lt;/span&gt;
  &lt;span class="c1"&gt;// Sort the slowest tests by duration&lt;/span&gt;
  &lt;span class="nx"&gt;slowestTests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a overall flake percentage for the run&lt;/span&gt;
  &lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flakePercentage&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="nx"&gt;flakyTests&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="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 8. Save the merged results to a file&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResultsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergedResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`✅ Merged test results saved at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mergedResultsFilePath&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="c1"&gt;// 9. Call the mergeResults function to execute the script&lt;/span&gt;
&lt;span class="nf"&gt;mergeResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Add the Script to the Pipeline&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that the &lt;code&gt;merge-results.js&lt;/code&gt; script is created, let’s utilize it in the CI/CD pipeline. Add a new merge job and ensure it runs after all Cypress run jobs have completed. Below is an example using GitHub Actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="c1"&gt;# Run Cypress Test Job&lt;/span&gt;
  &lt;span class="na"&gt;cypress-run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-runner&lt;/span&gt; &lt;span class="c1"&gt;# Your runner&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Number of parallel jobs, uses cypress-split&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# 1. Run Cypress&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Cypress Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run cy:run&lt;/span&gt; &lt;span class="c1"&gt;# Your run command&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SPLIT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ strategy.job-total }}&lt;/span&gt;
          &lt;span class="na"&gt;SPLIT_INDEX&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ strategy.job-index }}&lt;/span&gt; 
      &lt;span class="c1"&gt;# 2. Upload Cypress Results&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Cypress Results&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success() || failure()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress-results-${{ strategy.job-index }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results&lt;/span&gt; &lt;span class="c1"&gt;# Your results folder path&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

  &lt;span class="c1"&gt;# Merge Cypress Test Results Job&lt;/span&gt;
  &lt;span class="na"&gt;merge-test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-runner&lt;/span&gt; &lt;span class="c1"&gt;# Your runner&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cypress-run&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Ensure this job runs after all cypress run jobs have finished&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success() || failure()&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# 3. Download Cypress Results, since they're saved from the previous job&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Cypress Results&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress-results-*&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results&lt;/span&gt; &lt;span class="c1"&gt;# Your results folder path&lt;/span&gt;
          &lt;span class="na"&gt;merge-multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="c1"&gt;# 4. Merge Cypress Results - Call the merge script&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge Test Results&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node your-folder/cypress/merge-results.js&lt;/span&gt; &lt;span class="c1"&gt;# Your merge script path&lt;/span&gt;
      &lt;span class="c1"&gt;# 5. Upload Merged Results &lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Merged Report&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;overwrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-folder/cypress/results/merged-results.json&lt;/span&gt; &lt;span class="c1"&gt;# Your merged results output file&lt;/span&gt;
          &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The high-level job structure should look something 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%2Fx1mvuapz2jjqsw5jhidt.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%2Fx1mvuapz2jjqsw5jhidt.png" alt=" " width="720" height="262"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Use the Report&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each time your pipeline runs, it will now generate a merged JSON file as a pipeline artifact to be downloaded. Here’s a sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;merged-results.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalDuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"45.21 min"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalPassed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalFailed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalPending"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalSkipped"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flakePercentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flakyTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 1 - should create a new user"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 12 - should delete an existing user"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slowestTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 1 - Test Number 14 - should edit an existing user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"214.14"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 3 - Test Number 2 - should paginate the user list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"174.29"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Group 21 - Test Number 3 - should submit a new bill for an existing user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AssertionError: Timed out retrying after 4000ms: expected '27251' to equal '27,251'"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test Number 13 -  should invoice a user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CypressError: Timed out retrying after 7500ms: `cy.wait()` timed out waiting `7500ms` for the 1st request to the route: `interceptMyUser`."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this data in a single file, your team can quickly identify flaky tests, bottlenecks, and failures without digging through multiple CI job logs. This report is easy to share and provides direct and clean insight making it crucial as part of the CI/CD troubleshooting process.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By generating your own test analytics, you unlock insight into test reliability and performance — without relying on third-party tools. This kind of visibility is essential to keeping a large test suite healthy and maintainable.&lt;/p&gt;

&lt;p&gt;Now that this initial data is available, for future iterations one could consider expanding the logic to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate alerts for flake thresholds or long runtimes&lt;/li&gt;
&lt;li&gt;Track results across multiple runs and upload into an internal dashboard&lt;/li&gt;
&lt;li&gt;Identify differences between previous result health to spot immediate regressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need a full observability platform to spot trends — just a clever script and a bit of CI magic. Looking forward to hearing your metric implementations.&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Cypress - 7 Features I Wish the Native Test Runner Had (And How to Work Around Them)</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Fri, 07 Mar 2025 03:38:50 +0000</pubDate>
      <link>https://dev.to/cydavid/cypress-7-features-i-wish-the-native-test-runner-had-and-how-to-work-around-them-1ip7</link>
      <guid>https://dev.to/cydavid/cypress-7-features-i-wish-the-native-test-runner-had-and-how-to-work-around-them-1ip7</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.cypress.io/app/get-started/why-cypress" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; is one of the best tools for end-to-end testing, offering a fast, developer-friendly experience with built-in waiting, an intuitive API, and great debugging capabilities. However, like any tool, it has its limitations. Some of these are &lt;a href="https://docs.cypress.io/app/guides/cypress-studio#Limitations" rel="noopener noreferrer"&gt;intentional design decisions&lt;/a&gt;, while others feel like omissions that would significantly improve the native test runner.&lt;/p&gt;

&lt;p&gt;Fortunately, the strong Cypress community has developed plugins, workarounds, and best practices to help overcome these challenges. Below, I’ll outline seven features I wish Cypress had natively and the ways to work around these shortcomings.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. Parallel Test Execution (without Cypress Cloud)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress supports parallel test execution, but only if you use the &lt;a href="https://docs.cypress.io/cloud/features/smart-orchestration/parallelization" rel="noopener noreferrer"&gt;Cypress Cloud Service&lt;/a&gt;. While Cypress Cloud is a fantastic, paid feature that provides a slick visual dashboard for improved debugging, analytics, and overall test health, parallelization bundled with the service feels like an unnecessary variable in today’s testing world. With other major testing competitors offering this feature out-of-the-box and considering how high-importance executing tests simultaneously is, this limitation is a big blocker for many teams. Thankfully, there are plugin workarounds to achieve parallel test execution without Cloud.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bahmutov/cypress-split" rel="noopener noreferrer"&gt;cypress-split&lt;/a&gt; — easy installation and use. Updated frequently.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tnicola/cypress-parallel" rel="noopener noreferrer"&gt;cypress-parallel&lt;/a&gt; — similar to split but frequently less updates.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Extended Test Filtering&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Native &lt;a href="https://docs.cypress.io/app/references/command-line#Options" rel="noopener noreferrer"&gt;Cypress CLI commands&lt;/a&gt; only allow filtering by spec files, meaning there is no built-in way to tag or filter tests based on test name or labels like &lt;code&gt;@smoke&lt;/code&gt; or &lt;code&gt;@regression&lt;/code&gt;. This is limiting for those that want to avoid organizing their test specs and folders entirely based off of test runs. Additionally, teams may need to run subsets of tests within the same spec file at different points in the Software Development Lifecycle. Once again, there are plugins to help achieve this.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/cypress-io/cypress/tree/develop/npm/grep#readme" rel="noopener noreferrer"&gt;@cypress/grep&lt;/a&gt; plugin to enable filtering on titles or tags&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/cypress-io/cypress-grep" rel="noopener noreferrer"&gt;cypress-grep&lt;/a&gt; plugin. Similar to &lt;code&gt;@cypress/grep&lt;/code&gt; but more frequently updated.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/dennisbergevin/cypress-cli-select" rel="noopener noreferrer"&gt;cypress-cli-select&lt;/a&gt; plugin to easily execute specific specs by file, name or tag in the cli. Built off of &lt;code&gt;cypress-grep&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. Test Runner Filtering (Live Filtering in UI)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Continuing the filtering trend, there’s no easy way to execute a single test or group of tests within the Test Runner itself. The workaround to this is to go into your code and add a &lt;code&gt;.only&lt;/code&gt; to the &lt;code&gt;it&lt;/code&gt; or &lt;code&gt;describe&lt;/code&gt; methods but this requires back and forth between the test runner and the code. For those encouraging non-QA engineers to run Cypress tests locally, having a way to filter tests without knowing the test code is useful. Simply, the Test Runner does not offer a UI-based way to filter tests dynamically while running them.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/dennisbergevin/cypress-plugin-grep-boxes" rel="noopener noreferrer"&gt;cypress-plugin-grep-boxes&lt;/a&gt; plugin to start filtering within the test runner without code changes. Built off of &lt;code&gt;cypress-grep&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/dennisbergevin/cypress-plugin-last-failed" rel="noopener noreferrer"&gt;cypress-plugin-last-failed&lt;/a&gt; plugin to easily re-run the last failed test(s). Also supports CLI. Built off of &lt;code&gt;cypress-grep&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. Official Safari/WebKit Support&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress &lt;a href="https://docs.cypress.io/app/references/launching-browsers#Browser-versions-supported" rel="noopener noreferrer"&gt;officially supports&lt;/a&gt; Chrome, Edge, and Firefox, but not Safari or WebKit, making it difficult to test apps in an &lt;em&gt;environment&lt;/em&gt; closer to Apple’s ecosystem. However, despite a lack of official support, there’s experimental support built on top of the &lt;code&gt;playwright-webkit&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;This is a great option that is better than nothing but there are still a handful of &lt;a href="https://github.com/cypress-io/cypress/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22experiment%3A+webkit%22" rel="noopener noreferrer"&gt;known issues&lt;/a&gt; that could be blockers for a complicated test suite, such as missing &lt;code&gt;cy.origin&lt;/code&gt; support.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="http://npmjs.com/package/playwright-webkit" rel="noopener noreferrer"&gt;playwright-webkit&lt;/a&gt; and add experimentalWebKitSupport: true to your config file. Run Cypress as usual using the &lt;code&gt;--browser webkit&lt;/code&gt; cli param.&lt;/li&gt;
&lt;li&gt;Pray you don’t need to test against Safari…kidding but sometimes hard-blockers require alternative testing tools as a last result.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. Multi-Browser Support&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress is designed to run within a single browser tab, making it difficult to test workflows that involve multiple tabs or popups. There’s no easy command to switch between multiple tabs or browser contexts to accomplish multi-user functionality such as a chat box. That being said, Cypress has a ton of workarounds that still provide full-test confidence without needing to truly simulate two browser context.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;cy.request&lt;/code&gt; to &lt;a href="https://docs.cypress.io/app/references/trade-offs#Multiple-browsers-open-at-the-same-time" rel="noopener noreferrer"&gt;simulate receiving requests&lt;/a&gt; from another user.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/cypress-io/cypress/tree/develop/npm/puppeteer" rel="noopener noreferrer"&gt;@cypress/puppeteer&lt;/a&gt; plugin to support switching to a new tab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://filiphric.com/opening-a-new-tab-in-cypress" rel="noopener noreferrer"&gt;Validate the new tab&lt;/a&gt; without truly redirecting to the new tab&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. Real User Interactions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress primarily relies on synthetic events for interacting with the DOM, which typically achieve most common, user interactions. However some events, such as simulating hover states and drag-and-drop actions, aren’t easily achievable with this default behavior. Once again, the community has provided a handful of useful plugins to make user interactions a bit easier.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Utilize the &lt;a href="https://github.com/dmtrKovalenko/cypress-real-events" rel="noopener noreferrer"&gt;cypress-real-events&lt;/a&gt; plugin to fire real, native events such as &lt;code&gt;realHover&lt;/code&gt; and &lt;code&gt;realClick&lt;/code&gt;. Only works for chromium-based browsers.&lt;/li&gt;
&lt;li&gt;For drag and drop, use &lt;a href="https://github.com/4teamwork/cypress-drag-drop" rel="noopener noreferrer"&gt;cypress-drag-drop&lt;/a&gt; or &lt;a href="https://docs.cypress.io/api/commands/trigger#Trigger-a-mousedown-from-a-specific-mouse-button" rel="noopener noreferrer"&gt;manually dispatch&lt;/a&gt; &lt;code&gt;mousedown&lt;/code&gt;/&lt;code&gt;mousemove&lt;/code&gt;/&lt;code&gt;mouseup&lt;/code&gt; events.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7. Command Retry Logic&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress has fantastic &lt;a href="https://docs.cypress.io/app/core-concepts/retry-ability#Only-queries-are-retried" rel="noopener noreferrer"&gt;built-in assertion retry-ability&lt;/a&gt; but currently does not support retries for non-query actions, such as commands. On theme, there are a handful of workarounds to extend the default Cypress functionality including a must-use plugin.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Workarounds:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;a href="https://github.com/NoriSte/cypress-wait-until" rel="noopener noreferrer"&gt;cypress-wait-until&lt;/a&gt; plugin for retrying non-query logic. Adds waiting power to &lt;em&gt;literally everything&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Utilize the &lt;a href="https://github.com/bahmutov/cypress-recurse" rel="noopener noreferrer"&gt;cypress-recurse&lt;/a&gt; plugin to retry custom commands until a predicate function returns true. Another great alternative.&lt;/li&gt;
&lt;li&gt;Convert your custom command into a &lt;a href="https://docs.cypress.io/api/cypress-api/custom-queries" rel="noopener noreferrer"&gt;custom query&lt;/a&gt; instead which supports retrievability. Cypress v12 and above, not recommended for every command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;While Cypress is an incredibly powerful testing tool, it has limitations that can sometimes slow down test automation efforts. Thankfully, the strong community and ecosystem of plugins provide workarounds for most missing features. It just requires a bit of searching and additional setup.&lt;/p&gt;

&lt;p&gt;As Cypress continues to evolve, I hope to see more of these features become natively supported. Until then, hopefully this list serves as a useful conglomeration of workarounds to help make the most of your Cypress test automation efforts.&lt;/p&gt;

&lt;p&gt;As always, happy testing!🚀&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>tutorial</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Cypress: (Help)ers Wanted — When and Where to Use Them</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Fri, 07 Mar 2025 03:24:16 +0000</pubDate>
      <link>https://dev.to/cydavid/cypress-helpers-wanted-when-and-where-to-use-them-30e7</link>
      <guid>https://dev.to/cydavid/cypress-helpers-wanted-when-and-where-to-use-them-30e7</guid>
      <description>&lt;p&gt;When organizing your Cypress helpers, should they go in a utility file, become a custom command or remain inline? Do I even need a helper, or can I repeat the functionality in multiple specs? These questions might seem small, but they’re crucial as your test suite grows.&lt;/p&gt;

&lt;p&gt;As your automation test suite grows, organizing helpers becomes critical. Without proper structure, you risk creating inconsistent implementations, duplicated code, and increased maintenance overhead. These challenges lead to slower development cycles and decrease confidence in the test suite’s reliability. A well-structured and predictable framework enables multiple engineers to work collaboratively, ensuring consistency across tests. But how do you know where to define a test helper?&lt;/p&gt;

&lt;p&gt;Before we get to the &lt;em&gt;where&lt;/em&gt;, let’s discuss the why. Helpers in automation testing service two primary purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encapsulating common or repeated operations to reduce duplicated code.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving readability by abstracting complex or lengthy logic.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result of this, all helpers you write should address one or both of these benefits. Reducing duplication — following the &lt;a href="https://www.getdbt.com/blog/guide-to-dry" rel="noopener noreferrer"&gt;Don’t Repeat Yourself&lt;/a&gt;(DRY) principle — is a common software development practice that should carry over into automation testing. While DRY can be taken to extremes (where maintaining the abstraction becomes more work than its value), automation testers should focus on removing duplicated code to improve maintainability, scalability, and predictability.&lt;/p&gt;

&lt;p&gt;Additionally, some helpers might just be useful for readability. For example, instead of populating a form with ten (or more) fields directly in a test, you might prefer a single call to &lt;code&gt;populateNewUserForm()&lt;/code&gt;. This abstraction makes the test cleaner and easier to understand at a glance. In the example below the test is easy to read at a high-level due to the helper but still provides the user easy access to it if needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;populateNewUserForm&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="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;first-name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;David&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;last-name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Ingraham&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;gender-select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;male&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;email-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;test@gmail.com&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;password-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;happy testing&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;address-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;123 Testing Street&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;address-2-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Unit 1&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;city-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Denver&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;state-select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Colorado&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;zip-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;12345&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;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;Create User&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 create a user&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;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Populate new user form&lt;/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;create-user-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="nf"&gt;populateNewUserForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Submit User&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit-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;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@postUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;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="nf"&gt;expect&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="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&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;Once you’ve identified the use for a helper, the next step is deciding where to define it. In Cypress, there are three primary options, each suited for different scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Single Spec&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Helpers that are only relevant to one test or spec file should be defined within that file. This keeps the scope limited and avoids unnecessary abstraction.&lt;/p&gt;

&lt;p&gt;An example of this might be a populate helper unique to a single spec or a re-used piece of logic unique to that file. We can expand our example above and add another single, spec helper called &lt;code&gt;waitForUserTableToLoad&lt;/code&gt; since it’s used multiple times only for this one 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;populateNewUserForm&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="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;first-name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;David&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;last-name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Ingraham&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;gender-select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;male&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;email-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;test@gmail.com&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;password-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;happy testing&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;address-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;123 Testing Street&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;address-2-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Unit 1&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;city-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;Denver&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;state-select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Colorado&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;zip-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;12345&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitForUserTableToLoad&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getUsers&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;user-table-loading-spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="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;Create User&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 add a user&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;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getUsers&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;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;waitForUserTableToLoad&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Populate new user form&lt;/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;create-user-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="nf"&gt;populateNewUserForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Submit User&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit-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;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@postUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;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="nf"&gt;expect&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="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;waitForUserTableToLoad&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;user-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David Ingraham&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Validate user persists after reload&lt;/span&gt;
      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;waitForUserTableToLoad&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;user-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David Ingraham&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;The helper reduces duplicated code while keeping the test readability, two traits that are once again crucial for maintaining a test suite as it grows. If either one of these helpers are needed across multiple specs, then you might want to consider moving it to an external helper file instead.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Helper File&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If a helper is used across multiple spec files, it should be defined or moved to a shared &lt;code&gt;helpers&lt;/code&gt; or &lt;code&gt;utils&lt;/code&gt; folder within your Cypress project. This centralizes logic, making it easier to maintain and update. An example of a shared helper could include anything ranging from re-used setup code, validations or calculations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Shared Navigation Helper&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigateToUserPage&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getUsers&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;login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Login - Custom Command&lt;/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;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@getUsers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Shared Validation Helper&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateUserTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&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;user-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&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;firstName&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;lastName&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="c1"&gt;// Shared Calculation Helper&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calculateTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&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;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.12&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;shipping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.99&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;priceTax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;shipping&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;priceTax&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;All three of these examples are now available to be imported into multiple specs across our test suite. If any issues or updates occur, updating them is a breeze as we only have to make changes in a single place without worrying about touching multiple test files.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Custom Commands&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, Cypress is unique because it provides a built-in helper called a &lt;a href="https://docs.cypress.io/api/cypress-api/custom-commands" rel="noopener noreferrer"&gt;Custom Command&lt;/a&gt;. They are ideal for helpers that represent common actions, validations or setup that’s shared across a majority or all specs and tests. Cypress Commands offer several advantages over plain javascript helper files including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global Availability:&lt;/strong&gt; No need to import them into every spec file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chaining Support:&lt;/strong&gt; Ability to chain off other Cypress Commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return Values:&lt;/strong&gt; Able to return a value using .then&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I have the following login function and defined it as both a Cypress Command and a Javascript helper, it might look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Custom Command &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;login&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;user&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&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;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="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;/login&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authToken&lt;/span&gt;&lt;span class="dl"&gt;'&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;token&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;// JS Helper&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="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;/login&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authToken&lt;/span&gt;&lt;span class="dl"&gt;'&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;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the code is nearly identical, utilizing this code in a spec is a bit different. For a Cypress Custom Command, I have access to my &lt;code&gt;login&lt;/code&gt; command by calling &lt;code&gt;cy.login&lt;/code&gt; as it’s now globally available to all my files versus having to import my login JS helper across all specs. Further, if my &lt;code&gt;login&lt;/code&gt; helper returns user information, I can easily access it by doing:&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;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="c1"&gt;// You CAN do this&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If I need to use `userInfo` in my tests, I can do so&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// You CANT do this&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;login&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;Accessing a returned value isn’t possible with a regular helper due to the &lt;a href="https://learn.cypress.io/cypress-fundamentals/understanding-the-asynchronous-nature-of-cypress" rel="noopener noreferrer"&gt;asynchronous nature&lt;/a&gt; of how Cypress works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Cypress also has the option to create &lt;a href="https://docs.cypress.io/api/cypress-api/custom-queries" rel="noopener noreferrer"&gt;Custom Queries&lt;/a&gt;. These are type of command that follow a slightly different set of rules than Custom Commands. These won’t be discussed specifically, but they are available as needed.&lt;/p&gt;

&lt;p&gt;The Custom Commands I define fall into one of the following three categories &lt;strong&gt;API&lt;/strong&gt;, &lt;strong&gt;Validation&lt;/strong&gt; and &lt;strong&gt;Action&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;API Custom Commands&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;These are helpers that simply wrap API calls which are needed for test setup. Programmatically controlling test data using &lt;code&gt;cy.request&lt;/code&gt; is a best practice for maintaining test data reliably. Defining this setup in a custom command allows any tests that utilize it to access any created API data as needed. For example, let’s define a Custom Command that programmatically creates a user for our test and allows us to access the created user response for further validation and use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create User POST Custom Command&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;createUser&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;Cypress&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createUser&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="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;/user&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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="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="c1"&gt;// Test Example&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 edit an existing user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Programatically create the user and fetch the response &lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&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;createdUser&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;firstName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createdUser&lt;/span&gt;

     &lt;span class="c1"&gt;// Use the real response to validate application data&lt;/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;edit-user-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;first-name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstName&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;For more information on this pattern, check out my article &lt;a href="https://dev.to/cydavid/cypress-programmatically-control-test-data-3c6e"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Validation Custom Commands&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;These type of custom commands are shared assertions reused across all test files. The benefit of a custom command is that these might be validations that can be chained off a previous cypress selector command.&lt;/p&gt;

&lt;p&gt;An example of this could be &lt;code&gt;validateMenuTabLists&lt;/code&gt; which is a helper that validates a particular page has the correct tab list and text. The implementation might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Validation Custom Command &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;validateMenuListTabs&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;prevSubject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;element&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tabs&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;// Validate list tab length&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="tab"&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.of&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tabs&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="c1"&gt;// Validate individual tab texts&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="tab"]&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;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tab&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="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;// Test Example&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 user 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;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;user-page-menu-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;validateMenuListTabs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Info&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;Billing&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;Settings&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;This is extremely valuable as validating any tab list in my application is now simple and accessible. Defining common validations is powerful because you additionally gain confidence that all your elements are verified the same.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Action Custom Commands&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, action command are those that &lt;em&gt;do something&lt;/em&gt; throughout the test.&lt;/p&gt;

&lt;p&gt;A popular example might be defining a custom, &lt;code&gt;cy.get&lt;/code&gt; command such as &lt;code&gt;getByTestId&lt;/code&gt;. In this example, the command forces the user to grab an element using a custom, &lt;code&gt;data-test&lt;/code&gt; &lt;a href="https://docs.cypress.io/app/core-concepts/best-practices#Selecting-Elements" rel="noopener noreferrer"&gt;attribute instead of other methods&lt;/a&gt;. Another example could be defining a custom dropdown, select helper for a particular framework such as &lt;code&gt;selectPrimeVueOption&lt;/code&gt; for the &lt;a href="https://primevue.org/" rel="noopener noreferrer"&gt;PrimeVue&lt;/a&gt; component library. Defining custom commands that slightly tweak the default Cypress command behavior to work with your preferred framework and patterns provides a huge benefit across all of your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Action Custom Command - Customized Get&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;getByDataTest&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;dataTest&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-test=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dataTest&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;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Action Custom Command - Customized Dropdown Select&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;selectPrimeVueOption&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;prevSubject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;element&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;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;scrollIntoView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-pc-section="dropdown"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropdownButton&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;@dropdownButton&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="c1"&gt;// expand dropdown&lt;/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;.p-select-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Select dropdown option&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Test Example&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 select user data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByDataTest&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-age-select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;selectPrimeVueOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30&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;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Wherever you put your helper, it should always enhance your test suite’s &lt;strong&gt;maintainability&lt;/strong&gt;, &lt;strong&gt;scaleability&lt;/strong&gt; and &lt;strong&gt;readability&lt;/strong&gt;. Good helpers provide these benefits while poorly structured helpers add unnecessary complexity. Ultimately though, there’s &lt;em&gt;not one right answer&lt;/em&gt;, just suggested organization. Use your best judgement based on the scope of impact and level of effort to maintain the helper if something changes. Having this awareness and purpose for all your Single Spec helpers, Helper Files, or Custom Commands, creates a robust and organized Cypress test suite that grows gracefully with your application and sets your team up for long-term success.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;closingMessage&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thanks for Reading, Happy Testing.&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;connect with me on LinkedIn and say hi&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;closingMessage&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;



</description>
      <category>cypress</category>
      <category>tutorial</category>
      <category>automation</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
