<?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: Daniil</title>
    <description>The latest articles on DEV Community by Daniil (@daniil-qa).</description>
    <link>https://dev.to/daniil-qa</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%2F2566700%2F11d7df4e-7d39-41ae-9c08-c9f707f1bdac.png</url>
      <title>DEV Community: Daniil</title>
      <link>https://dev.to/daniil-qa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daniil-qa"/>
    <language>en</language>
    <item>
      <title>How to handle drag and drop with Cypress in Workflow Builder</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Tue, 13 Jan 2026 03:49:13 +0000</pubDate>
      <link>https://dev.to/daniil-qa/how-to-handle-drag-and-drop-with-cypress-in-workflow-builder-cfi</link>
      <guid>https://dev.to/daniil-qa/how-to-handle-drag-and-drop-with-cypress-in-workflow-builder-cfi</guid>
      <description>&lt;p&gt;Drag’n’drop is one of those UI interactions that used to feel painful to automate.&lt;br&gt;
Rarely used, often avoided… but very real in modern products, especially a Agentic Workflow Builders like Opus or n8n, OpenAI, Monday.com, etc.&lt;/p&gt;

&lt;p&gt;Workflow builders, canvas editors, node-based UIs — like Opus — rely on it heavily.&lt;/p&gt;

&lt;p&gt;The good news: Cypress (with the right approach) handles this surprisingly well.&lt;/p&gt;

&lt;p&gt;Two main ways to do drag’n’drop in Cypress&lt;/p&gt;
&lt;h2&gt;
  
  
  Native browser events
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.trigger("mousedown")&lt;/code&gt;&lt;br&gt;
&lt;code&gt;.trigger("mousemove")&lt;/code&gt;&lt;br&gt;
&lt;code&gt;.trigger("mouseup")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This gives you full control over coordinates and is perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canvas-based UIs&lt;/li&gt;
&lt;li&gt;node connectors&lt;/li&gt;
&lt;li&gt;custom drag logic&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Cypress-real-events plugin
&lt;/h2&gt;

&lt;p&gt;Closer to real user behavior and great for classic UI drag cases.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-world example: connecting nodes on a canvas.
&lt;/h2&gt;

&lt;p&gt;This is how I automate workflow connections in Opus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Cypress Custom Command to connect workflow units - Tasks(Nodes).

Cypress.Commands.add("connectNodes", (outputSelector, inputSelector, numberOfEdges) =&amp;gt; {
  function getCenterCoords($el) {
    const rect = $el[0].getBoundingClientRect();
    return {
      x: rect.x + rect.width / 2,
      y: rect.y + rect.height / 2,
    };
  }

  cy.get(outputSelector).then(($out) =&amp;gt; {
    const { x: outX, y: outY } = getCenterCoords($out);

    cy.get(inputSelector).then(($in) =&amp;gt; {
      const { x: inX, y: inY } = getCenterCoords($in);

      cy.wrap($out)
        .trigger("mousedown", { button: 0, clientX: outX, clientY: outY, force: true })
        .trigger("mousemove", { clientX: inX, clientY: inY, force: true });

      cy.wrap($in).trigger("mouseup", { force: true });

      cy.get("[data-test-id='edge']")
        .should("exist")
        .and("have.length", numberOfEdges);
    });
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach lets tests build workflows automatically, just like real users do.&lt;/p&gt;

&lt;p&gt;And bonus: file uploads.&lt;br&gt;
While it seems to be the same method, it really depends on which packages your product is using. So there is 2 approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.cypress.io/blog/uploading-files-with-selectfile" rel="noopener noreferrer"&gt;.selectFile()&lt;/a&gt; - built-in way to select files in an HTML5 input element &amp;amp; simulate dragging files into a browser with the .selectFile() &lt;/li&gt;
&lt;li&gt;Native browser events:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;.trigger("dragenter")&lt;/code&gt;&lt;br&gt;
&lt;code&gt;.trigger("dragover")&lt;/code&gt;&lt;br&gt;
&lt;code&gt;.trigger("drop")&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Cypress Custom Command to drag'n'drop file into upload area, e.g. React Aria DropZone

Cypress.Commands.add(
  "dragAndDropUploadFile",
  (fileName, targetSelector, mimeType = "application/json") =&amp;gt; {
    cy.intercept("POST", "/job/file/download").as("fileDownload");

    cy.fixture(`sampleFiles/${fileName}`, "base64").then((fileContent) =&amp;gt; {
      const blob = Cypress.Blob.base64StringToBlob(fileContent, mimeType);
      const testFile = new File([blob], fileName, { type: mimeType });

      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(testFile);

      cy.get(targetSelector)
        .trigger("dragenter", { dataTransfer })
        .trigger("dragover", { dataTransfer })
        .trigger("drop", { dataTransfer });
    });
    // Verify status of the upload process with custom command
    cy.verifyApiStatus("@fileDownload", 201);
  },
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key takeaway&lt;/p&gt;

&lt;p&gt;Drag’n’drop isn’t just about moving pixels.&lt;br&gt;
It’s about testing user intent, state transitions, and real AI workflows.&lt;/p&gt;

&lt;p&gt;Once you stop avoiding it — Cypress gives you everything you need.&lt;/p&gt;

&lt;p&gt;Next up: more canvas, workflow, and agentic UI testing patterns 👀&lt;/p&gt;

&lt;p&gt;How do you handle drag’n’drop in your tests?&lt;/p&gt;

</description>
      <category>testing</category>
      <category>cypress</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>cy.prompt() Changes UI Testing Forever. How it works under the hood</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Tue, 14 Oct 2025 12:50:38 +0000</pubDate>
      <link>https://dev.to/daniil-qa/cyprompt-changes-ui-testing-forever-how-it-works-under-the-hood-5f2l</link>
      <guid>https://dev.to/daniil-qa/cyprompt-changes-ui-testing-forever-how-it-works-under-the-hood-5f2l</guid>
      <description>&lt;h2&gt;
  
  
  The Pain We’ve All Felt
&lt;/h2&gt;

&lt;p&gt;If you’ve ever tested a Shadow DOM component or a slotted element, you know the pain.&lt;/p&gt;

&lt;p&gt;Finding the right selector sometimes feels like archaeology - digging through browser dev tools, retries, custom commands in the dev console, and then one day… a UI redesign breaks it all again…&lt;/p&gt;

&lt;p&gt;I’ve personally spent hours writing helpers just to find one stubborn element inside a Shadow Root which was slotted at the same time.&lt;br&gt;
And every time, I thought:&lt;/p&gt;

&lt;p&gt;“There must be a simpler way to tell the test what I mean.”&lt;/p&gt;
&lt;h2&gt;
  
  
  The Breakthrough - &lt;code&gt;cy.prompt()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I think some QA Gods and #cypress product team heard me. Now, there is &lt;code&gt;cy.prompt()&lt;/code&gt; !&lt;br&gt;
With &lt;code&gt;cy.prompt()&lt;/code&gt;, you can literally describe what to test, and Cypress with AI assistance will translate it into executable test code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//test-login.spec.js

cy.prompt([
  "Navigate to '/login' // with baseUrl set
  "Fill email in the shadow email input and submit"
  "Verify error message 'Password is required' is displaying below the password input field in the shadow DOM"
]);

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

&lt;/div&gt;



&lt;p&gt;Cypress runs this step, finds stable selectors under the hood, and caches them for speed.&lt;br&gt;
If the DOM changes, it can self-heal by regenerating selectors automatically.&lt;/p&gt;

&lt;p&gt;No more guessing, no more brittle selectors, no more “why did this break again?”.&lt;/p&gt;
&lt;h2&gt;
  
  
  Shadow DOM &amp;amp; Slotted Elements Example
&lt;/h2&gt;

&lt;p&gt;Before I had to write custom Cypress commands just to get correct slotted UI element from a Shadow DOM. And my code looked 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%2Fxan6z8lxabeiwmj3e3hl.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%2Fxan6z8lxabeiwmj3e3hl.png" alt="Cypress-custom-command-for-slotted-element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then I had to use that in my test&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;And now...wait for it...&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//test-login.spec.js

cy.prompt("Fill email in the shadow email input and submit");

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

&lt;/div&gt;



&lt;p&gt;Same action, one line — and it understands nested DOM structures too.&lt;br&gt;
That’s what makes this release so huge.&lt;/p&gt;
&lt;h2&gt;
  
  
  🥒 Works with Gherkin &amp;amp; Cucumber
&lt;/h2&gt;

&lt;p&gt;If you’re using Cucumber and have a perfect-written Gherkin from you PM or PO, this is where it gets really interesting.&lt;/p&gt;

&lt;p&gt;Cypress can now map your plain English steps directly to real actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gherkin

Given I log in with a valid user

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

&lt;/div&gt;



&lt;p&gt;can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.prompt("Log in with a valid user")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;But I would still recommend to use the Golden Rule: Don’t mix business rules and test code in plain text.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You still want your custom commands and good test design - &lt;code&gt;cy.prompt()&lt;/code&gt; is a boost, not a replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;                   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex, verbose test code&lt;/li&gt;
&lt;li&gt;Heavy selector maintenance&lt;/li&gt;
&lt;li&gt;Manual test updates&lt;/li&gt;
&lt;li&gt;Cucumber integration needs mapping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One clear, natural-language step&lt;/li&gt;
&lt;li&gt;Auto-generated, cached selectors&lt;/li&gt;
&lt;li&gt;AI-assisted self-healing&lt;/li&gt;
&lt;li&gt;AI maps English → test commands&lt;/li&gt;
&lt;li&gt;AI suggestions if your prompt is weak for execution&lt;/li&gt;
&lt;li&gt;Cashed runs if DOM not changed → better test performance &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current Limitations
&lt;/h2&gt;

&lt;p&gt;It’s not magic (yet):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Works only with UI, not API assertions.&lt;/li&gt;
&lt;li&gt;Still experimental — needs your feedback.&lt;/li&gt;
&lt;li&gt;Generated code should be reviewed before committing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But even now, it’s a massive leap forward in software test automation experience, though!&lt;/p&gt;




&lt;p&gt;&lt;code&gt;cy.prompt()&lt;/code&gt; feels like the moment automation starts listening to us.&lt;br&gt;
It bridges natural language, AI, and test logic — while keeping the power of Cypress intact.&lt;/p&gt;

&lt;p&gt;As someone who fought Shadow DOM selectors for years, I can honestly say that I’ve never written reliable UI tests this fast before.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://bit.ly/3KRTcV9" rel="noopener noreferrer"&gt;Finding out what's next for cy.prompt()&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would you use &lt;code&gt;cy.prompt()&lt;/code&gt; in your daily workflows or wait until it matures a bit more?&lt;/p&gt;

&lt;p&gt;Follow my Telegram Channel for more insights. I share everything there first: &lt;a href="https://t.me/softwaretestersnotes" rel="noopener noreferrer"&gt;Software Tester's Notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And my YouTube Channel:&lt;br&gt;


  &lt;iframe src="https://www.youtube.com/embed/To8yjel37KE"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>javascript</category>
      <category>webtesting</category>
      <category>qa</category>
    </item>
    <item>
      <title>🥷 Shadow DOM in Test Automation: A Practical Guide with Cypress</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Wed, 24 Sep 2025 05:56:46 +0000</pubDate>
      <link>https://dev.to/daniil-qa/shadow-dom-in-test-automation-a-practical-guide-with-cypress-39o0</link>
      <guid>https://dev.to/daniil-qa/shadow-dom-in-test-automation-a-practical-guide-with-cypress-39o0</guid>
      <description>&lt;p&gt;When testing web apps, the DOM is not always as straightforward as it looks.&lt;br&gt;
Sometimes the element you “see” in DevTools isn’t accessible to your test. Why? Because it’s wrapped inside iframes or shadow DOM.&lt;/p&gt;

&lt;p&gt;In this article, let’s break down what shadow DOM is, why it matters, and how you can test it with Cypress.&lt;/p&gt;
&lt;h1&gt;
  
  
  What is Shadow DOM?
&lt;/h1&gt;

&lt;p&gt;Shadow DOM is an encapsulated subtree of the DOM that lives inside a host element.&lt;/p&gt;

&lt;p&gt;It was introduced as part of the Web Components spec and is widely used in modern UI libraries (Material UI, Ionic, Lit, Angular components).&lt;/p&gt;

&lt;p&gt;Key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS and JS are scoped (no global leaks)&lt;/li&gt;
&lt;li&gt;Components are reusable&lt;/li&gt;
&lt;li&gt;Structure is hidden from the “main” DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as a mini DOM inside the DOM.&lt;/p&gt;
&lt;h1&gt;
  
  
  Types of Shadow DOM
&lt;/h1&gt;
&lt;h3&gt;
  
  
  Normal DOM (baseline)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="login"&amp;gt;
  &amp;lt;input id="username" /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get('#username').type('hello')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shadow DOM (open)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;my-login&amp;gt;
  #shadow-root (open)
    &amp;lt;input id="username" /&amp;gt;
&amp;lt;/my-login&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option A: Use &lt;code&gt;.shadow()&lt;/code&gt; in Cypress&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get('my-login')
  .shadow()
  .find('#username')
  .type('hello')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option B: Enable globally in cypress.config.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  e2e: {
    includeShadowDom: true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shadow DOM (closed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;my-login&amp;gt;
  # — - &amp;gt; shadow-root (closed)
    &amp;lt;input id="username" /&amp;gt;
&amp;lt;/my-login&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Closed shadow roots cannot be accessed with Cypress or regular query selectors.&lt;br&gt;
They are designed to be private.&lt;br&gt;
&lt;strong&gt;Workaround:&lt;/strong&gt; you’ll need to use component-level testing, or rely on app exposing test hooks (e.g. props or attributes).&lt;/p&gt;
&lt;h3&gt;
  
  
  Slotted Elements
&lt;/h3&gt;

&lt;p&gt;Sometimes elements are “projected” into a shadow DOM via a slot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;my-login&amp;gt;
  #shadow-root (open)
    &amp;lt;slot name="email"&amp;gt;&amp;lt;/slot&amp;gt;
&amp;lt;/my-login&amp;gt;

&amp;lt;input slot="email" id="email-input" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom Cypress command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cypress.Commands.add('getSlottedElement', (selector) =&amp;gt; {
  return cy.get(selector).then(($el) =&amp;gt; {
    return cy.wrap($el[0].assignedNodes()[0])
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.getSlottedElement('#email-input').type('hello@qa.com')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Shadow DOM Matters in Automation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It’s everywhere in modern SPAs and Web Components&lt;/li&gt;
&lt;li&gt;Your tests may fail with “element not found” unless you account for it&lt;/li&gt;
&lt;li&gt;Handling shadow DOM properly makes tests stable and future-proof&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remember that:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM ≠ always accessible elements&lt;/li&gt;
&lt;li&gt;Web apps can include: plain DOM, iframes, Shadow DOM (open/closed)&lt;/li&gt;
&lt;li&gt;Cypress supports open shadow DOM with .shadow() or includeShadowDom config property&lt;/li&gt;
&lt;li&gt;For slotted elements, you may need custom helpers. &lt;code&gt;.shadow()&lt;/code&gt; can’t see slotted content directly, because it lives outside the shadow root and is just projected into it&lt;/li&gt;
&lt;li&gt;For closed shadow DOM, collaborate with devs for test hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re new to automation:&lt;br&gt;
Start experimenting with Cypress and a simple Web Component demo.&lt;br&gt;
You’ll quickly see why handling &lt;br&gt;
shadow DOM is a must have skill.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webtesting</category>
    </item>
    <item>
      <title>Why your Telegram bot test is failing silently</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Tue, 27 May 2025 05:47:27 +0000</pubDate>
      <link>https://dev.to/daniil-qa/why-your-telegram-bot-test-is-failing-silently-nb3</link>
      <guid>https://dev.to/daniil-qa/why-your-telegram-bot-test-is-failing-silently-nb3</guid>
      <description>&lt;p&gt;Telegram app logo is seen in this illustration taken, Aug 27, 2024.&lt;br&gt;
Image: Reuters file&lt;/p&gt;

&lt;p&gt;Sometimes the problem isn’t in your test code — it’s in how Telegram handles updates.&lt;/p&gt;

&lt;p&gt;If your bot is running locally in polling mode, and your test also tries to read updates — nothing will break… but nothing will work either.&lt;/p&gt;

&lt;p&gt;Let me explain what happened — and why this behavior is easy to overlook when you're just starting to test Telegram bots as myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was writing a test for the &lt;code&gt;/start&lt;/code&gt; command using &lt;code&gt;pytest&lt;/code&gt;.&lt;br&gt;
The bot was running locally, the token was valid, everything looked fine — but the test was just...waiting. &lt;/p&gt;

&lt;p&gt;Turns out, the issue wasn’t in the code — it was in the way Telegram’s &lt;code&gt;getUpdates&lt;/code&gt; API works.&lt;/p&gt;

&lt;p&gt;Here’s what was really going on:&lt;/p&gt;

&lt;p&gt;My bot was running with &lt;code&gt;.run_polling()&lt;/code&gt;, which continuously polls &lt;code&gt;getUpdates&lt;/code&gt; in the background.&lt;br&gt;
My test was also calling &lt;code&gt;getUpdates&lt;/code&gt; manually, expecting to retrieve the &lt;code&gt;/start&lt;/code&gt; command from a real Telegram chat.&lt;br&gt;
The result? A hidden conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telegram only allows one active consumer of updates.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So when the bot is running, it “eats” all updates — and the test sees nothing.&lt;/p&gt;

&lt;p&gt;🛠️ How to Fix It&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;you can’t simulate a user sending &lt;code&gt;/start&lt;/code&gt; directly through the Telegram API (that’s forbidden)&lt;/strong&gt;, here are two reliable alternatives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Manual Trigger + Stored Log Verification&lt;/strong&gt;&lt;br&gt;
Best for CI or webhook-based bots.&lt;/p&gt;

&lt;p&gt;You manually send &lt;code&gt;/start&lt;/code&gt; from Telegram.&lt;br&gt;
Your bot processes it and logs the result.&lt;br&gt;
Your test waits briefly, then checks the log file or database to confirm the expected output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Simulate Telegram webhook with a fake payload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fully automated way using &lt;code&gt;requests.post()&lt;/code&gt; or similar to mock a real Telegram update:&lt;/p&gt;

&lt;p&gt;You run your bot in webhook mode (instead of polling).&lt;br&gt;
Telegram would normally send POST requests to your webhook URL (e.g. &lt;code&gt;https://abc123.ngrok-free.app/webhook&lt;/code&gt;)&lt;br&gt;
Instead, your test simulates this POST request using your own payload.&lt;br&gt;
Your Flask or FastAPI app receives it and behaves as if it came from Telegram.&lt;/p&gt;

&lt;p&gt;But why do I need Flask or FastAPI at all?&lt;/p&gt;

&lt;p&gt;When using webhooks, Telegram sends updates to your bot as HTTP POST requests.&lt;/p&gt;

&lt;p&gt;To receive them — you need a web server.&lt;/p&gt;

&lt;p&gt;That’s where Flask or FastAPI comes in.&lt;br&gt;
They expose an endpoint like &lt;code&gt;/webhook&lt;/code&gt; that can receive and process Telegram’s payloads.&lt;/p&gt;

&lt;p&gt;Webhook flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Telegram sends an update:
POST &lt;code&gt;https://yourdomain.com/webhook&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Flask/FastAPI receives it and hands it over to python-telegram-bot&lt;/li&gt;
&lt;li&gt;Your handler function is called just like in polling mode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without this, your bot simply won’t work in webhook mode — and you won’t be able to test real-world flows in CI/CD.&lt;/p&gt;

&lt;p&gt;Testing Telegram bots has its nuances and bottlenecks, and this one was a surprise for me too.&lt;/p&gt;

&lt;p&gt;But the deeper you go — the more power you unlock.&lt;br&gt;
This case alone pushed me to explore better testing strategies and architect a more production-like setup, even for test automation.&lt;/p&gt;

&lt;p&gt;Would you be interested in more deep-dives on Telegram bot testing?&lt;/p&gt;

&lt;p&gt;Let me know in the comments or drop a DM — I’m currently building a courses on this topic using Pytest, Playwright, and Cypress.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>telegram</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Parallelizing Cypress Tests in CI/CD with Jenkins, Docker, and K8s</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Tue, 22 Apr 2025 14:06:34 +0000</pubDate>
      <link>https://dev.to/daniil-qa/parallelizing-cypress-tests-in-cicd-with-jenkins-docker-and-k8s-29pb</link>
      <guid>https://dev.to/daniil-qa/parallelizing-cypress-tests-in-cicd-with-jenkins-docker-and-k8s-29pb</guid>
      <description>&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%2Fb3uw3w60s3wfpny3m22z.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%2Fb3uw3w60s3wfpny3m22z.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Learn how to run Cypress tests in parallel using Jenkins CI, Docker, and Kubernetes.&lt;br&gt;
There are plenty of guides out there showing how to run Cypress tests in parallel using CI/CD tools like GitHub Actions, GitLab CI, CircleCI, and Jenkins.&lt;/p&gt;

&lt;p&gt;But what if I told you that you can take it one step further—and integrate Kubernetes?&lt;br&gt;
Yes, you read that right. You can run your tests across multiple pods to massively speed up execution using K8s.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔧 Tools Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cypress Dashboard (for parallelization logic).&lt;/li&gt;
&lt;li&gt;Jenkins CI (or any similar CI like GitHub Actions).&lt;/li&gt;
&lt;li&gt;Docker (to pin Node/Chrome versions).&lt;/li&gt;
&lt;li&gt;Kubernetes (to run multiple Cypress pods).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;│cypress/
│   └── e2e/
│       └── testsuite1.spec.js
│       └── ……
├── cypress.config.js
├── Dockerfile
├── Jenkinsfile
└── k8s/
    └── cypress-job.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: Cypress setup with parallelization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a basic Cypress project on Cypress Cloud to enable parallelization logic&lt;/li&gt;
&lt;li&gt;Grab your project ID and record key.&lt;/li&gt;
&lt;li&gt;Follow Cypress Dashboard rules to connect your Cypress framework to the Dashboard by explicitly defining project ID in your config file and adding cypress key to your CLI command that run test execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;cypress.config.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { defineConfig } = require("cypress");

module.exports = defineConfig({
  // Your projectId from Cypress Dashboard project settings:
  projectId: "xyz123", 
  e2e: {
    ……
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your CLI command will look something like this &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx cypress run --headless --record --key ${{your-secure-record-key-from-Cypress-Dashboard}} --parallel --group regression --ci-build-id ${{BUILD_NUMBER_GENERATED_BY_CI_PROVIDER}} —-env url=${{test-env-variable}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I use additional flag &lt;code&gt;—-env url=&lt;/code&gt; since I run tests on multiple environments. But you can exclude that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create a Dockerfile
&lt;/h2&gt;

&lt;p&gt;I normally use original Dockerfile from Cypress itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cypress-io/cypress-docker-images" rel="noopener noreferrer"&gt;GitHub link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hub.docker.com/r/cypress/included/" rel="noopener noreferrer"&gt;Docker image&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dockerfile

# Use Cypress included image matching package.json version
FROM cypress/included:14.0.1

# Set working directory to root (where config files reside)
WORKDIR /

# Copy package files and all config files to root
COPY package.json package-lock.json cypress.*.config.js ./

# Install dependencies and ensure Cypress binary is downloaded
RUN npm ci &amp;amp;&amp;amp; \
    npx cypress install --force &amp;amp;&amp;amp; \
    ls -l /root/.cache/Cypress/14.0.1/Cypress/Cypress || echo "Binary install failed"

# Copy the rest of the project files
COPY . .

# Default environment variables (can be overridden)
ARG ENVIRONMENT=stage
ARG CYPRESS_KEY
ARG TEST_FILE=""

ENV CYPRESS_ENVIRONMENT=$ENVIRONMENT
ENV CYPRESS_RECORD_KEY=$CYPRESS_KEY
ENV TEST_FILE=$TEST_FILE

# Default command (will be overridden in pipeline)
CMD ["npx", "cypress", "run", "--headless", "--record", "--key", "$CYPRESS_RECORD_KEY", "--env", "url=$CYPRESS_ENVIRONMENT"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create a Jenkinsfile
&lt;/h2&gt;

&lt;p&gt;In my case I re-use common template that has been made for each and every repo CI pipeline on our product, so I just needed to refer to shared lib.&lt;br&gt;
But you follow your own company policies. The Jenkins template would be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;groovy

pipeline {
  agent any
  environment {
    CYPRESS_RECORD_KEY = credentials('cypress-key')
    BUILD_ID = "${env.BUILD_NUMBER}"
  }
  stages {
    stage('Install') {
      steps {
        sh 'npm ci'
      }
    }
    stage('Run Cypress Tests in Parallel') {
                steps {
                    script {
                        def environmentToUse = params.ENV_NAME ?: 'stage'
                        def keyToUse = environmentToUse == 'stage' ? cypressKey : cypressKeyDev
                        def configFile = "cypress.${environmentToUse}.config.js"
                        echo "Using Cypress config file: ${configFile} for environment: ${environmentToUse}"

                        def testFiles = sh(script: """#!/bin/sh
                            find cypress/e2e -name '*.spec.js' 2&amp;gt;/dev/null || echo "No test files found"
                        """, returnStdout: true).trim().split('\n').findAll { it &amp;amp;&amp;amp; it != "No test files found" }

                        if (!testFiles) {
                            error "No Cypress test files (*.spec.js) found in cypress/e2e directory!"
                        }
                        echo "Found ${testFiles.size()} test files: ${testFiles.join(', ')}"
                        def numContainers = Math.min(testFiles.size(), 8)

                        def parallelTasks = [:]
                        for (int i = 0; i &amp;lt; numContainers; i++) {
                            def startIdx = (i * testFiles.size() / numContainers).toInteger()
                            def endIdx = ((i + 1) * testFiles.size() / numContainers).toInteger()
                            def subset = testFiles[startIdx..&amp;lt;endIdx]
                            def podLabel = "cypress-test-${UUID.randomUUID().toString()}"

                            parallelTasks["cypress-${i}"] = {
                                podTemplate(label: podLabel, yaml: """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: cypress
    image: ${imageName}
    command:
    - cat
    tty: true
    resources:
      requests:
        memory: "2Gi"
        cpu: "1"
      limits:
        memory: "3Gi"
        cpu: "1.5"
""") {
                                    node(podLabel) {
                                        container('cypress') {
                                            sh script: """#!/bin/sh
                                                ls -l /root/.cache/Cypress/14.0.1/Cypress/Cypress || echo "Binary not found"
                                                ls -l /${configFile} || echo "Config file not found"
                                                cd /
                                                npx cypress run --headless --record --key ${keyToUse} --parallel --group "regression" --ci-build-id "${env.BUILD_NUMBER}" --env url=${environmentToUse} --config-file ${configFile}
                                                echo "${env.BUILD_NUMBER}"
                                            """
                                        }
                                    }
                                }
                            }
                        }
                        parallel parallelTasks
                    }
                }
            }
      }
    }
  }
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Define a Kubernetes Job
&lt;/h2&gt;

&lt;p&gt;The most complicated step I would say.&lt;/p&gt;

&lt;p&gt;Cypress test command should remain consistent between your Jenkinsfile and the Kubernetes Deployment or Job YAML file.&lt;br&gt;
You may ask Why? Because Cypress uses the &lt;code&gt;--record&lt;/code&gt;, &lt;code&gt;--parallel&lt;/code&gt;, and &lt;code&gt;--ci-build-id&lt;/code&gt; flags to coordinate and split specs across machines (or pods in our case).&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jenkins is responsible for triggering the build and setting environment variables.&lt;/li&gt;
&lt;li&gt;Kubernetes is responsible for running the actual test command inside pods.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 4a: How to securely pass CI_BUILD_ID and CYPRESS_RECORD_KEY
&lt;/h3&gt;

&lt;p&gt;You &lt;strong&gt;should not hard-code sensitive values&lt;/strong&gt; in YAML files or source control.&lt;/p&gt;

&lt;p&gt;Use Kubernetes secrets instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash

kubectl create secret generic cypress-secrets \
  --from-literal=CYPRESS_RECORD_KEY=your-record-key \
  --from-literal=CI_BUILD_ID=your-ci-build-id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference them in your YAML file as environment variables&lt;br&gt;
&lt;code&gt;k8s/cypress-job.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yaml

env:
  - name: CYPRESS_RECORD_KEY
    valueFrom:
      secretKeyRef:
        name: cypress-secrets
        key: CYPRESS_RECORD_KEY
  - name: CI_BUILD_ID
    valueFrom:
      secretKeyRef:
        name: cypress-secrets
        key: CI_BUILD_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your test command inside the container, use the same npx cypress run ... as in Jenkins.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: cypress-parallel-job
spec:
  parallelism: 3
  completions: 3
  template:
    spec:
      containers:
        - name: cypress-runner
          image: cypress/browsers:node-18.12.0-chrome-123.0.6312.86-1
          command: ["/bin/sh", "-c"]
          args:
            - &amp;gt;
              npm ci &amp;amp;&amp;amp;
              npx cypress run \
              --record \
              --key $(CYPRESS_RECORD_KEY) \
              --parallel \
              --group "regression" \
              --ci-build-id $(CI_BUILD_ID) \
              --browser chrome
          env:
            - name: CYPRESS_RECORD_KEY
              valueFrom:
                secretKeyRef:
                  name: cypress-secrets
                  key: CYPRESS_RECORD_KEY
            - name: CI_BUILD_ID
              valueFrom:
                secretKeyRef:
                  name: cypress-secrets
                  key: CI_BUILD_ID
      restartPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;parallelism: 3&lt;/code&gt; - Defines how many pods run in parallel &lt;br&gt;
&lt;code&gt;completions: 3&lt;/code&gt; - Defines how many successful completions are required for the job to be considered complete. Each successful pod counts as one completion.&lt;/p&gt;

&lt;p&gt;Normally, Cypress Dashboard will show you how many machines are needed for the fastest execution&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%2F4rjec0ckiwa88heuiwzi.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%2F4rjec0ckiwa88heuiwzi.png" alt="Image description" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT: Each pod in this setup runs the same command, so to avoid duplicating test effort, you need Cypress’s --parallel and --ci-build-id logic to coordinate test distribution across those pods. They have to use same command and same secrets&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4b: 🚀 How Kubernetes runs multiple pods for parallel tests
&lt;/h3&gt;

&lt;p&gt;When using &lt;code&gt;--parallel&lt;/code&gt; in Cypress and deploying tests via Kubernetes, each pod acts as one parallelized "machine" for Cypress Dashboard to split test specs.&lt;/p&gt;

&lt;p&gt;You can use a Kubernetes Job or Deployment with a &lt;code&gt;replicaCount&lt;/code&gt; or control pod spawning using a custom script:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Static replicas (simpler)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec:
  replicas: 4 # Number of parallel test runners
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option B: Dynamic pods based on spec count (advanced)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can write a pre-step script (in Jenkins or elsewhere) that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Counts the number of Cypress spec files.&lt;/li&gt;
&lt;li&gt;Decides how many pods are needed.&lt;/li&gt;
&lt;li&gt;Adjusts the Job YAML using &lt;code&gt;envsubst&lt;/code&gt; or &lt;code&gt;sed&lt;/code&gt;, or a Helm chart.&lt;/li&gt;
&lt;li&gt;Applies the modified YAML via &lt;code&gt;kubectl apply -f&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Managing memory &amp;amp; resource allocation
&lt;/h2&gt;

&lt;p&gt;Use resources in YAML to allocate CPU and memory for each pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yaml

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
  limits:
    memory: "1Gi"
    cpu: "500m"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Cypress tests are heavy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with 1Gi per pod and adjust based on OOM kills or slowness.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Node Autoscaler&lt;/strong&gt; to spin up new nodes if cluster can't handle the load.&lt;/li&gt;
&lt;li&gt;Or use &lt;strong&gt;Cluster Autoscaler on AWS EKS / GCP GKE / Azure AKS&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🛠️ &lt;strong&gt;Bonus: Auto-Scaling with a custom script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a script (Node.js, Bash, or Python) to:&lt;/p&gt;

&lt;p&gt;This lets you scale parallel runs dynamically without hardcoding pod counts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect the number of &lt;code&gt;.spec.js&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Set a max pod count per node.&lt;/li&gt;
&lt;li&gt;Patch a YAML template using &lt;code&gt;envsubst&lt;/code&gt; or a Helm chart.&lt;/li&gt;
&lt;li&gt;Apply it to the cluster with &lt;code&gt;kubectl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Running Cypress tests in parallel isn’t just an optimization - it’s a game changer when it comes to test execution time and CI/CD scalability.&lt;/p&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cypress Cloud’s built-in parallelization logic&lt;/li&gt;
&lt;li&gt;Jenkins for CI orchestration&lt;/li&gt;
&lt;li&gt;Docker to lock down your test environment&lt;/li&gt;
&lt;li&gt;Kubernetes to scale execution across multiple pods&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;you get a powerful, maintainable, and cloud-native testing setup.&lt;/p&gt;

&lt;p&gt;🔐 Secure parameter passing with Kubernetes Secrets&lt;br&gt;
⚡ Dynamically spin up multiple pods for faster test runs&lt;br&gt;
💡 Keep Cypress logic consistent across local, CI, and K8s&lt;/p&gt;

&lt;p&gt;Whether you're scaling out your test suite, trying to reduce feedback loops, or modernizing your dev pipeline—this approach gives you the tools to move fast without breaking things.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>cypress</category>
      <category>kubernetes</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getters vs. functions in Cypress: The best practice you need to know</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Mon, 17 Feb 2025 19:39:48 +0000</pubDate>
      <link>https://dev.to/daniil-qa/getters-vs-functions-in-cypress-the-best-practice-you-need-to-know-25j7</link>
      <guid>https://dev.to/daniil-qa/getters-vs-functions-in-cypress-the-best-practice-you-need-to-know-25j7</guid>
      <description>&lt;p&gt;Recently I've observed that some of us, QA Automation engineers, are still using &lt;em&gt;getters&lt;/em&gt; in their test automation frameworks with Cypress. &lt;br&gt;
When working with the Page Object Model in Cypress (Yes, you could use POMs if your web application is big, has a lot of micro services, micro frontends, and plenty of configurations and settings), you might come across two ways to define element selectors: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;getters (&lt;code&gt;get&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;functions (&lt;code&gt;method()&lt;/code&gt;). &lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Cypress works differently from Selenium &amp;amp; Playwright
&lt;/h2&gt;

&lt;p&gt;Cypress executes commands asynchronously, meaning .get() does not return an element immediately like in Selenium or Playwright. Instead, Cypress commands are added to an internal queue and resolved automatically when Cypress executes them.&lt;br&gt;
It means that &lt;em&gt;getters&lt;/em&gt; won’t work as expected when trying to interact with elements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of a getter (incorrect approach in Cypress)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LoginPage {
  get usernameField() {
    //Cypress does NOT return an element immediately:
    return cy.get('#username'); 
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fails because Cypress does not return an immediate element reference. Instead, it returns a Cypress chainable object, which requires further chaining for interactions like &lt;code&gt;.type()&lt;/code&gt;, &lt;code&gt;.click()&lt;/code&gt;, or &lt;code&gt;.should()&lt;/code&gt;. Instead, they return a Cypress chain that must be resolved in Cypress' command queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;em&gt;getters&lt;/em&gt; fail in Cypress
&lt;/h2&gt;

&lt;p&gt;Using getters in Cypress is problematic because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cypress commands are not immediately resolved&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Cypress chains commands and executes them sequentially. &lt;em&gt;Getters&lt;/em&gt; try to return an element immediately, which breaks Cypress' execution model.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Getters&lt;/em&gt; create unexpected behavior&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Since Cypress commands don’t return actual values, writing like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoginPage.usernameField.type('testuser') 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will fail because &lt;code&gt;usernameField&lt;/code&gt; does not return an immediate element.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lack of explicit execution&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Cypress follows a declarative approach, where you describe what should happen, and Cypress handles the execution. &lt;em&gt;Getters&lt;/em&gt; hide Cypress commands inside properties, making it harder to debug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The correct approach: use functions instead
&lt;/h2&gt;

&lt;p&gt;Instead of &lt;em&gt;getters&lt;/em&gt;, define element selectors as &lt;em&gt;functions&lt;/em&gt;. This ensures Cypress correctly waits for elements before interacting with them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Correct Page Object Model with function approach&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Option 1 - Returning a Cypress command for chaining&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LoginPage {
  usernameField() {
    return cy.get('#username');
  }

  passwordField() {
    return cy.get('#password');
  }

  loginButton() {
    return cy.get('#login');
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can use it correctly in your tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const loginPage = new LoginPage();
loginPage.usernameField().type('testuser');
loginPage.passwordField().type('password');
loginPage.loginButton().click();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures Cypress waits for elements to be available before performing actions.&lt;/p&gt;

&lt;p&gt;Let me add something here and explain 1 more thing...&lt;/p&gt;

&lt;p&gt;Option 2 - Cleaner and better - Performing an action inside the function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LoginPage {
  usernameField() {
    //Works without return:
    cy.get('#username').type("123");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, would you use &lt;code&gt;return&lt;/code&gt; or not?&lt;/p&gt;

&lt;p&gt;Use return when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need to perform multiple actions in a test (&lt;code&gt;.type()&lt;/code&gt;, &lt;code&gt;.clear()&lt;/code&gt;, &lt;code&gt;.should()&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;you want to chain commands later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use return when:&lt;/p&gt;

&lt;p&gt;The function directly executes an action inside it (like &lt;code&gt;.type("123")&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Just compare the next two rows and look which is better and cleaner for your test scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Option 1:
loginPage.usernameField().clear().type("testuser")

// Option 2:
loginPage.enterUsername("testuser")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparison: &lt;em&gt;getters&lt;/em&gt; vs &lt;em&gt;functions&lt;/em&gt; in Cypress
&lt;/h2&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%2Fmcg1civzqk597libd8q8.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%2Fmcg1civzqk597libd8q8.png" alt="Table" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When can &lt;em&gt;getters&lt;/em&gt; work in Cypress?
&lt;/h2&gt;

&lt;p&gt;If you are migrating from Selenium or (for some reason) from Playwright you might still want to use &lt;em&gt;getters&lt;/em&gt;. Then you must return a function inside the &lt;em&gt;getter&lt;/em&gt;. However, this adds unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Your POM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LoginPage {
  get usernameField() {
    //This will work but unnecessary complexity:
    return () =&amp;gt; cy.get('#username'); 
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loginPage.usernameField().type('testuser');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this technically works, using &lt;em&gt;functions&lt;/em&gt; directly is a cleaner solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Avoid &lt;em&gt;getters&lt;/em&gt; in Cypress POMs because Cypress commands don’t return immediate elements.&lt;/li&gt;
&lt;li&gt;Use functions to ensure Cypress properly executes commands and handles automatic waiting.&lt;/li&gt;
&lt;li&gt;By switching from getters to functions, you’ll write cleaner, more maintainable Cypress tests that follow its execution model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have you ever faced issues with getters in Cypress or you think it still better than using functions? Drop your thoughts in the comments below!&lt;/p&gt;

&lt;p&gt;Check out more about chaining on &lt;a href="https://learn.cypress.io/cypress-fundamentals/command-chaining" rel="noopener noreferrer"&gt;official Cypress portal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webtesting</category>
      <category>cypress</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cypress assertions. What to use: cy.should() or expect()?</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Fri, 07 Feb 2025 20:06:23 +0000</pubDate>
      <link>https://dev.to/daniil-qa/cypress-assertions-what-to-use-cyshould-or-expect-p19</link>
      <guid>https://dev.to/daniil-qa/cypress-assertions-what-to-use-cyshould-or-expect-p19</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Before we talked about aliases and storing values in hybrid web automation frameworks. Now, let's touch another topic and understand in what cases we should use Chai assertions &lt;code&gt;should()&lt;/code&gt; and &lt;code&gt;expect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Cypress, you have two main ways to perform assertions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cypress built-in assertions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.wrap($element).should('be.visible')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Chai assertions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect($element).to.be.visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each method has its own advantages and drawbacks. Let’s break them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cypress assertions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.wrap($element).should('be.visible')

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic retries:&lt;/strong&gt; Cypress will keep retrying the assertion until the element meets the expected condition or the test times out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better handling of asynchronous behavior:&lt;/strong&gt; Since Cypress is designed to work with asynchronous operations, &lt;code&gt;should()&lt;/code&gt; ensures the assertion runs only when the element is available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chainable:&lt;/strong&gt; You can chain Cypress commands with &lt;code&gt;.should()&lt;/code&gt; directly, making your code more readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works well with Cypress commands:&lt;/strong&gt; Since Cypress commands are asynchronous, using &lt;code&gt;.should()&lt;/code&gt; ensures that Cypress waits for the command to complete before making assertions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More verbose:&lt;/strong&gt; Requires wrapping elements with &lt;code&gt;cy.wrap()&lt;/code&gt;, which can make the code slightly longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slightly different debugging:&lt;/strong&gt; Since Cypress retries automatically, it might not immediately fail, making debugging take a bit longer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get('.button').should('be.visible');
cy.wrap($element).should('be.visible');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Chai assertions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect($element).to.be.visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple and direct:&lt;/strong&gt; Less code needed, making it more concise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works well with synchronous operations:&lt;/strong&gt; If you already have a reference to an element and don’t need Cypress’s automatic retries, &lt;code&gt;expect()&lt;/code&gt; works well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No automatic retry:&lt;/strong&gt; If the element isn’t immediately visible, the assertion fails instantly instead of waiting for it to appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn’t work well with Cypress commands:&lt;/strong&gt; Since Cypress commands are asynchronous, trying to use &lt;code&gt;expect()&lt;/code&gt; directly on a Cypress command might lead to timing issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less reliable in UI tests:&lt;/strong&gt; Since UI elements might take time to appear due to animations, rendering delays, or network requests, lack of retries can lead to flaky tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get('.button').then(($btn) =&amp;gt; {
  expect($btn).to.be.visible; // Fails instantly if not visible
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which one is better?
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;cy.should()&lt;/code&gt; as the default assertion method in Cypress tests because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cypress automatically retries the assertion.&lt;/li&gt;
&lt;li&gt;It works seamlessly with Cypress’s asynchronous nature.&lt;/li&gt;
&lt;li&gt;It reduces flaky tests by waiting for the condition to be met.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When to use &lt;code&gt;expect()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you’re asserting a pure JavaScript object instead of a Cypress-wrapped element.&lt;/li&gt;
&lt;li&gt;When checking values inside a &lt;code&gt;.then()&lt;/code&gt; block (e.g., API responses, calculations, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of correct &lt;code&gt;expect()&lt;/code&gt; usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.request('/api/users/1').then((response) =&amp;gt; {
  expect(response.status).to.eq(200);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best practice: Stick to one for consistency
&lt;/h2&gt;

&lt;p&gt;For better code consistency, maintainability, and readability, use Cypress assertions &lt;code&gt;.should()&lt;/code&gt; in most cases.&lt;br&gt;
However, allow &lt;code&gt;expect()&lt;/code&gt; only when dealing with non-Cypress elements (e.g., API responses, custom JavaScript objects).&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Recommendation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;cy.should()&lt;/code&gt; for UI-related assertions.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;expect()&lt;/code&gt; only for non-Cypress elements (API responses, variables).&lt;/li&gt;
&lt;li&gt;Don’t mix both styles &lt;strong&gt;for the same purpose&lt;/strong&gt; to keep the framework clean and maintainable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;P.S. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have used the same approach in my working web automation hybrid framework and it works so well and performance increased.🚀&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>cypress</category>
      <category>testing</category>
      <category>webtesting</category>
    </item>
    <item>
      <title>Efficient way to store values in Cypress: Aliases vs cy.task</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Sun, 02 Feb 2025 10:39:25 +0000</pubDate>
      <link>https://dev.to/daniil-qa/efficient-way-to-store-values-in-cypress-aliases-vs-cytask-3nhp</link>
      <guid>https://dev.to/daniil-qa/efficient-way-to-store-values-in-cypress-aliases-vs-cytask-3nhp</guid>
      <description>&lt;p&gt;You may saw that in web automation frameworks we are using either Cypress aliases or &lt;code&gt;cy.task&lt;/code&gt; for storing values. &lt;br&gt;
What is the difference between &lt;code&gt;cy.task("saveItem", value)&lt;/code&gt; and Cypress aliases &lt;code&gt;cy.wrap().as()&lt;/code&gt;?&lt;br&gt;
It is a good question and it has pretty good baseline.&lt;br&gt;
The efficiency of saving an item in Cypress depends on the context of how you plan to use the saved values. &lt;br&gt;
Let’s compare the two approaches:&lt;/p&gt;
&lt;h2&gt;
  
  
  Using cy.task("saveItem", value)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Best for: Persistent storage or cross-test sharing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This method is used when you need to store data outside of Cypress's test execution environment, such as in a database, a file, or memory storage managed by the Node.js backend.&lt;/p&gt;

&lt;p&gt;It enables cross-test persistence, meaning the saved value can be accessed across different test runs.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cypress.Commands.add("saveItem", (value) =&amp;gt; {
  cy.task("saveItem", value);
});

it("Saves and retrieves data", () =&amp;gt; {
  cy.saveItem("myValue");
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using aliases cy.wrap(value).as("aliasName")
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Best for: Short-term, in-test storage&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Aliases are used within the same test or in before/beforeEach hooks but cannot be shared across test cases.&lt;/p&gt;

&lt;p&gt;Data stored in an alias is accessible via &lt;code&gt;this.aliasName&lt;/code&gt; in function callbacks or with &lt;code&gt;cy.get("@aliasName")&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it("Saves item using alias", function () {
  cy.wrap("myValue").as("savedItem");
  cy.get("@savedItem").then((item) =&amp;gt; {
    expect(item).to.equal("myValue");
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which is more efficient?
&lt;/h2&gt;

&lt;p&gt;If you need to store values across multiple tests or persist them beyond Cypress's in-memory execution, &lt;code&gt;cy.task(&lt;/code&gt;) is more efficient.&lt;br&gt;
If you only need to temporarily store data within a single test execution, aliases are more efficient because they are simpler and do not require interaction with the &lt;code&gt;Node.js&lt;/code&gt; process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;p&gt;Use aliases &lt;code&gt;cy.wrap().as()&lt;/code&gt; for temporary, within-test storage.&lt;br&gt;
Use &lt;code&gt;cy.task("saveItem", value)&lt;/code&gt; when data persistence beyond a single test execution is required.&lt;/p&gt;

&lt;p&gt;Let me know your approach to store values in Cypress! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>cypress</category>
      <category>webtesting</category>
    </item>
    <item>
      <title>Beyond Cypress `cy.intercept`: real-time SignalR websocket payments testing in Cypress hybrid frameworks</title>
      <dc:creator>Daniil</dc:creator>
      <pubDate>Sun, 02 Feb 2025 08:45:40 +0000</pubDate>
      <link>https://dev.to/daniil-qa/beyond-cypress-cyintercept-real-time-signalr-websocket-payments-testing-in-cypress-hybrid-2he5</link>
      <guid>https://dev.to/daniil-qa/beyond-cypress-cyintercept-real-time-signalr-websocket-payments-testing-in-cypress-hybrid-2he5</guid>
      <description>&lt;p&gt;In the first part of this &lt;a href="https://www.linkedin.com/pulse/beyond-cyintercept-real-time-websockets-testing-daniil-shapovalov-ep0if/?trackingId=ZTOhh6N%2BRd%2BU3n6wjMDvYQ%3D%3D" rel="noopener noreferrer"&gt;article&lt;/a&gt;, we explored the limitations of &lt;code&gt;cy.intercept&lt;/code&gt; for WebSocket testing and introduced a Cypress hybrid framework for handling real-time WebSocket interactions. Now, let’s dive into real-world payment testing scenarios, where WebSockets play a vital role in ensuring accurate transaction updates, live balance tracking, and seamless user experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WebSockets does matter for payment testing?
&lt;/h2&gt;

&lt;p&gt;Built-in payment systems in a web applications frequently rely on real-time updates to provide users with immediate feedback on their transactions. Unlike traditional HTTP polling, which repeatedly requests data from the server, WebSockets enable instant push notifications, making them ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live transaction status updates (e.g., "Payment Processing", "Payment Successful", "Payment Failed").&lt;/li&gt;
&lt;li&gt;Real-time balance updates after deposits, withdrawals, or refunds.&lt;/li&gt;
&lt;li&gt;Instant fraud alerts and security notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since Cypress doesn’t natively support WebSocket testing, we need to extend its capabilities using custom commands and WebSocket event listeners. This section will walk through specific payment testing scenarios, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validating real-time payment status updates.&lt;/li&gt;
&lt;li&gt;Ensuring balance updates after transactions.&lt;/li&gt;
&lt;li&gt;Handling error cases, disconnections, and retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-time SignalR connection for balance and payment status updates
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;connectWStoListen&lt;/code&gt; is a custom Cypress command is designed to establish a real-time WebSocket connection using SignalR. It listens for updates related to balance changes and payment status in applications that rely on WebSockets for instant transaction validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cypress.Commands.add(
  "connectWStoListen",
  (url, { onBalanceUpdate, onPaymentStatusUpdate }) =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
      const ws = new WebSocket(url);
      Cypress.env("currentWebSocket", ws); // Store WebSocket instance globally
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Introducing the handshake data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To establish a SignalR connection, we first send a handshake request to the server. This ensures that both client and server agree on communication protocols before exchanging actual data. The format is commonly used across the different types of a web sockets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...     
      const handshakeData = '{"protocol":"json","version":1}\x1e';
      let receivedEmptyResponse = false; // Track server's empty {} response
      let readyToListen = false; // Track when we're ready to listen for custom messages
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;\x1e&lt;/code&gt; at the end of &lt;code&gt;handshakeData&lt;/code&gt; is a special SignalR delimiter, marking the end of the message.&lt;/p&gt;

&lt;p&gt;Once the handshake is complete, we can &lt;strong&gt;start listening for messages&lt;/strong&gt; and &lt;strong&gt;handling errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling WebSocket messages &amp;amp; errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the connection is established, the SignalR receives real-time messages from the server. Our Cypress command processes these messages and handles different server responses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ws.onopen = () =&amp;gt; {
        console.log("SignalR WebSocket connection established");
        ws.send(handshakeData); // Step 1: Send handshake message

        ws.onmessage = (event) =&amp;gt; {
          const rawMessage = event.data;
          const messages = rawMessage.split("\x1e").filter((m) =&amp;gt; m);

          messages.forEach((message) =&amp;gt; {
            try {
              const parsedMessage = JSON.parse(message);

              // Step 2: Check for initial empty response {}
              if (
                Object.keys(parsedMessage).length === 0 &amp;amp;&amp;amp;
                !receivedEmptyResponse
              ) {
                console.log("Received empty response {} from server.");
                receivedEmptyResponse = true;
                ws.send('{"type":6}\x1e'); // Step 3: Send type after  empty server response
              }
              // Step 4: Wait for server to respond with {"type":6}
              else if (
                parsedMessage.type === 6 &amp;amp;&amp;amp;
                receivedEmptyResponse &amp;amp;&amp;amp;
                !readyToListen
              ) {
                console.log(
                  "Received server confirmation type 6. Ready for further messages.",
                );
                readyToListen = true;
                resolve(); // Signal that we're ready for payment deposit request
              }
              // Only listen for custom messages 
              // ReceivePaymentStatusUpdate and ReceiveBalanceUpdate
              // after setup is complete
              if (
                readyToListen &amp;amp;&amp;amp;
                parsedMessage.type === 1 &amp;amp;&amp;amp;
                parsedMessage.target
              ) {
                if (
                  parsedMessage.target === "ReceivePaymentStatusUpdate" &amp;amp;&amp;amp;
                  onPaymentStatusUpdate
                ) {
                  onPaymentStatusUpdate(parsedMessage.arguments[0]);
                }
                if (
                  parsedMessage.target === "ReceiveBalanceUpdate" &amp;amp;&amp;amp;
                  onBalanceUpdate
                ) {
                  onBalanceUpdate(parsedMessage.arguments[0]);
                }
              }
            } catch (error) {
              console.error("Failed to parse SignalR message:", error);
            }
          });
        };
        ws.onerror = (error) =&amp;gt; {
          console.log(WebSocket error: ${error.message});
          reject(error);
        };
        ws.onclose = () =&amp;gt; {
          console.log("SignalR WebSocket connection closed");
        };
      };
    });
  },
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error Handling:&lt;/strong&gt; The WebSocket gracefully logs errors and closes connections to prevent resource leaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Retrying:&lt;/strong&gt; If pending transactions remain unresolved, Cypress will retry until confirmation is received.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing WebSocket balance verification
&lt;/h2&gt;

&lt;p&gt;Now that our WebSocket connection is in place, we can create a dedicated method to validate real-time balance updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connectWStoVerifyBalance() {
      // Construct your WebSocket SignalR URL
      // The URL is based on configuration for specific testing 
      // environment
      const urlWS = `${envConfig.webSocketAPI}${bearerToken}`;
      cy.connectWStoListen(urlWS, {
        onPaymentStatusUpdate: (paymentStatus) =&amp;gt; {
          const parsedStatus = JSON.parse(paymentStatus);
          // Wait and retry if payment status is still 'pending'
          if (parsedStatus.status === "pending") {
            console.warn("Payment status is pending. Retrying...");
          }
          // Do an assertion that status is updated to Success
          expect(parsedStatus.status).to.equal("success");
          console.log("Payment status updated successfully:", parsedStatus);
        },
        onBalanceUpdate: (updatedBalance) =&amp;gt; {
          // Do required assertions for a balance update
          // Save new balance value if it is necessary
          console.log("Balance updated successfully:", updatedBalance);
          // Close WebSocket connection
          const ws = Cypress.env("currentWebSocket");
          if (ws) ws.close();
        },
      });
    });
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using WebSockets in a Cypress test case
&lt;/h2&gt;

&lt;p&gt;Now, let’s implement WebSocket validation in a Cypress test case to verify that payments are processed correctly and that balance updates happen in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe("Deposit Feature - Real-Time WebSocket Validation", () =&amp;gt; {

  it(`Verify that a user can deposit min amount and payment is successful`, () =&amp;gt; {
    // Step 1: User Registration &amp;amp; Setup
    cy.registerNewUser();
    cy.confirmUserIsRegistered();

    // Step 2: Establish WebSocket Connection for Balance Monitoring
    // using our created method
    cy.connectWStoVerifyBalance();

    // Step 3: Navigate to payments and do required actions to
    // initiate a deposit transaction
    cy.navigateToPayments();
    cy.accessDepositTab();
    cy.verifyMinimumDepositAmount();
    cy.validatePendingPaymentStatus();

    // Step 4: Simulate payment process via API/webhook/UI
    cy.updateDepositStatusViaAPI();

    // Step 5: Verify successful payment update on UI
    cy.validateSuccessfulPaymentMessage();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Since we've added console logs to our Cypress custom command &lt;code&gt;connectWStoListen&lt;/code&gt;, we will see real-time status updates in the Cypress console. This provides full visibility into how SignalR WebSocket connections handle transaction updates.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;WebSockets provide real-time transaction tracking for modern payment systems, ensuring that users get immediate feedback. By extending Cypress with custom WebSocket commands, we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test instant balance updates.&lt;/li&gt;
&lt;li&gt;Validate payment processing workflows.&lt;/li&gt;
&lt;li&gt;Ensure seamless, real-time UX in financial apps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Would you like to know more about Cypress and QA insights? Follow me on &lt;a href="https://www.linkedin.com/in/daniil-shapovalov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;🚀 or subscribe to my &lt;a href="https://t.me/softwaretestersnotes" rel="noopener noreferrer"&gt;TG channel&lt;/a&gt;🚀&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webautomation</category>
      <category>cypress</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
