<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Raju Dandigam</title>
    <description>The latest articles on Forem by Raju Dandigam (@raju_dandigam).</description>
    <link>https://forem.com/raju_dandigam</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%2F1726463%2F38d1e46f-d122-4fa3-b130-772169c24466.png</url>
      <title>Forem: Raju Dandigam</title>
      <link>https://forem.com/raju_dandigam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/raju_dandigam"/>
    <language>en</language>
    <item>
      <title>What Most Beginners Get Wrong About Building AI Apps</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Tue, 14 Apr 2026 05:31:37 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/what-most-beginners-get-wrong-about-building-ai-apps-32mg</link>
      <guid>https://forem.com/raju_dandigam/what-most-beginners-get-wrong-about-building-ai-apps-32mg</guid>
      <description>&lt;p&gt;When you first start building AI-powered features, everything sounds deceptively simple. You call an API, pass some text, and get a response back. After a few experiments, it starts to feel like all AI systems are built the same way.&lt;/p&gt;

&lt;p&gt;Then you hear terms like workflows, agents, and multi-agent systems, which only makes it more confusing. It is easy to assume these are just different names for the same thing.&lt;/p&gt;

&lt;p&gt;That assumption is where most beginners go wrong.&lt;/p&gt;

&lt;p&gt;Once you start building something real, something that needs to work consistently, scale, and handle edge cases, you quickly realize that these are fundamentally different ways of designing systems. The choice between them is not just about architecture. It directly affects reliability, cost, performance, and how easy your system is to debug when things break.&lt;/p&gt;

&lt;p&gt;The biggest mistake beginners make is not understanding how decisions are made inside their system.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s not really about AI; it’s about decision-making
&lt;/h2&gt;

&lt;p&gt;A much simpler way to think about AI systems is to ignore the model for a moment and focus on control.&lt;/p&gt;

&lt;p&gt;In some systems, you control every step. In others, the AI decides what to do next. In more complex setups, multiple AI components collaborate and share responsibilities.&lt;/p&gt;

&lt;p&gt;That difference in control is what shapes the entire system.&lt;/p&gt;

&lt;p&gt;If you design everything as if the AI should always “figure it out,” you will often end up with something harder to manage than it needs to be. If you over-control everything, you may limit flexibility where it actually matters.&lt;/p&gt;

&lt;p&gt;Understanding that balance early saves a lot of rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A relatable way to think about it
&lt;/h2&gt;

&lt;p&gt;Imagine you are ordering food.&lt;/p&gt;

&lt;p&gt;In one scenario, the process is completely structured. You select items from a menu, enter your address, confirm payment, and receive your order. Every step is predefined and predictable.&lt;/p&gt;

&lt;p&gt;In another scenario, you simply say, “I want something quick and healthy,” and the system figures out what you might like, asks follow-up questions, and adapts based on your answers.&lt;/p&gt;

&lt;p&gt;Now imagine a third scenario where one system understands your intent, another finds suitable options, and another optimizes delivery timing. Each part focuses on a specific responsibility, and together they complete the task.&lt;/p&gt;

&lt;p&gt;These three patterns represent very different ways of building AI applications, even though they might all use the same underlying model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simplest starting point: fixed decision paths
&lt;/h2&gt;

&lt;p&gt;Most real-world AI systems start with something very simple. You define the steps, and the system follows them every time.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;cleaned&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;cleanText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&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;generateSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleaned&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;keywords&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;extractKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is straightforward. Every execution follows the same sequence. If something fails, you know exactly where to look. If you need to optimize cost, you know how many model calls are happening. If you need to scale, the behavior is predictable.&lt;/p&gt;

&lt;p&gt;This is why many production systems rely heavily on this pattern. It works well for document processing, onboarding flows, reporting pipelines, and content moderation. These are all scenarios in which the steps are known ahead of time and do not change much across requests.&lt;/p&gt;

&lt;p&gt;Beginners often underestimate how powerful this approach is because it does not feel “intelligent.” In reality, this level of control is what makes systems reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the system needs to decide
&lt;/h2&gt;

&lt;p&gt;There are cases where predefined steps start to break down. You may not know the next step until you see the input. The system may need to explore, ask questions, or adapt based on context.&lt;/p&gt;

&lt;p&gt;That is where a different approach becomes useful.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, instead of defining the sequence, you define a goal and give the system a set of capabilities. The system decides whether it should search first, summarize later, or skip certain steps entirely.&lt;/p&gt;

&lt;p&gt;This flexibility is valuable in areas like customer support, research, and planning. Every input can be different, and the system needs to adapt rather than follow a fixed path.&lt;/p&gt;

&lt;p&gt;However, this comes with trade-offs. The number of steps may vary. The cost may vary. Debugging becomes less straightforward because the path is no longer fixed. You are trading control for flexibility.&lt;/p&gt;

&lt;p&gt;This is often where beginners run into trouble. It is tempting to use this approach everywhere because it feels more powerful, but many problems simply do not need that level of adaptability.&lt;/p&gt;

&lt;h2&gt;
  
  
  When complexity grows further
&lt;/h2&gt;

&lt;p&gt;As systems grow, you may find that a single decision-making unit becomes overloaded. Different parts of the task require different kinds of expertise. One part needs research, another needs writing, and another needs validation.&lt;/p&gt;

&lt;p&gt;At that point, splitting responsibilities can help.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;research&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;researchAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&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;draft&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;writerAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;research&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;final&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;editorAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&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;final&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;Each component focuses on a specific responsibility. One gathers information, another transforms it, and another refines it. This separation can improve quality and make complex tasks more manageable.&lt;/p&gt;

&lt;p&gt;At the same time, it introduces more moving parts. Coordination becomes important. Debugging becomes more complex. Costs can increase. This is why this pattern is usually introduced later rather than at the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where most beginners go wrong
&lt;/h2&gt;

&lt;p&gt;A common pattern I see is starting with the most flexible and complex approach first. It feels like the “correct” modern way to build AI systems.&lt;/p&gt;

&lt;p&gt;In practice, it often leads to overengineering.&lt;/p&gt;

&lt;p&gt;Simple tasks get wrapped in unnecessary complexity. Costs increase without clear benefits. Systems become harder to reason about. Small bugs become difficult to trace because the execution path is not fixed.&lt;/p&gt;

&lt;p&gt;Another mistake is forcing a rigid structure onto problems that clearly require flexibility. If your system keeps adding exceptions, retries, and conditional branches to handle different cases, it may be a sign that the design needs to allow more dynamic behavior.&lt;/p&gt;

&lt;p&gt;The real skill is not choosing one approach over another. It is knowing when each one makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  A more practical way to build AI systems
&lt;/h2&gt;

&lt;p&gt;Instead of picking one pattern and applying it everywhere, a better approach is to combine them.&lt;/p&gt;

&lt;p&gt;Start with a simple, controlled structure and introduce flexibility only where it adds value.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSupport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;type&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;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;simple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;searchFAQDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, straightforward questions are handled with a predictable path. More complex issues are handled with a flexible system that can adapt to the situation.&lt;/p&gt;

&lt;p&gt;This approach keeps the system efficient and understandable while still allowing intelligence where it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful mental model
&lt;/h2&gt;

&lt;p&gt;If you can clearly define the steps, keep it simple and structured.&lt;/p&gt;

&lt;p&gt;If the system needs to figure out the steps on its own, allow it more flexibility.&lt;/p&gt;

&lt;p&gt;If the problem naturally breaks into multiple specialized responsibilities, consider separating them.&lt;/p&gt;

&lt;p&gt;You do not need to start with the most advanced setup. In fact, starting simple often leads to better systems in the long run.&lt;/p&gt;

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

&lt;p&gt;The goal is not to build the smartest system.&lt;/p&gt;

&lt;p&gt;The goal is to build something that works reliably, is easy to understand, and can evolve as your requirements grow.&lt;/p&gt;

&lt;p&gt;Most successful AI applications are not fully autonomous systems. They are carefully designed combinations of control and flexibility.&lt;/p&gt;

&lt;p&gt;If you are just getting started, begin with something simple and predictable. Once you understand where your system needs more intelligence, add it deliberately.&lt;/p&gt;

&lt;p&gt;That approach will take you much further than trying to build the most advanced system on day one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Docker for TypeScript Developers Building AI Agents in 2026</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 00:08:52 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/docker-for-typescript-developers-building-ai-agents-in-2026-1k3l</link>
      <guid>https://forem.com/raju_dandigam/docker-for-typescript-developers-building-ai-agents-in-2026-1k3l</guid>
      <description>&lt;p&gt;Modern frontend engineers are no longer just building UI layers. Increasingly, we are building systems that orchestrate AI behavior. A simple TypeScript service can now act as a coordinator between large language models, vector databases, background workers, and external tools.&lt;/p&gt;

&lt;p&gt;That shift has quietly introduced a new class of problems. Not problems with writing code, but with running it.&lt;/p&gt;

&lt;p&gt;You might have already experienced something like this. Your AI agent works perfectly on your machine. It calls an LLM, stores context in a vector database, maybe uses Redis for memory, and even talks to a Python service for embeddings. Then a teammate pulls the repo and tries to run it.&lt;/p&gt;

&lt;p&gt;Suddenly, nothing works. Node versions don’t match. Python dependencies break. Redis isn’t running. Environment variables are missing. The system that felt simple is now fragile.&lt;/p&gt;

&lt;p&gt;This is where Docker stops being “infrastructure tooling” and becomes something much&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Problem Is Different in 2026
&lt;/h3&gt;

&lt;p&gt;Traditional web applications were mostly deterministic. If your code compiled and your dependencies matched, you could reasonably expect consistent behavior.&lt;/p&gt;

&lt;p&gt;AI systems don’t behave that way. Even when your code is correct, outcomes vary based on context, prompts, and external services. That makes the execution environment even more critical. If the environment itself is inconsistent, debugging becomes nearly impossible.&lt;/p&gt;

&lt;p&gt;On top of that, modern AI applications are rarely single-service systems. A typical setup might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A TypeScript API orchestrating agents&lt;/li&gt;
&lt;li&gt;A vector database for retrieval&lt;/li&gt;
&lt;li&gt;A cache or message queue for coordination&lt;/li&gt;
&lt;li&gt;A Python service for embeddings or model execution&lt;/li&gt;
&lt;li&gt;Optional local LLMs for development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is no longer just a Node.js app. It is a distributed system, even during development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker as the Execution Layer for AI Agents
&lt;/h3&gt;

&lt;p&gt;The most useful way to think about Docker in this context is not as a deployment tool, but as a boundary.&lt;/p&gt;

&lt;p&gt;Instead of letting your AI agent execute directly on your machine, you introduce a controlled environment where everything runs. The agent still makes decisions, but execution occurs within a container with defined tools, dependencies, and permissions.&lt;/p&gt;

&lt;p&gt;This separation solves several problems at once. It makes environments reproducible, isolates dependencies, and gives you a safe place for agents to run code, tests, or workflows.&lt;/p&gt;

&lt;p&gt;In practice, this means your TypeScript application becomes the orchestration layer, while Docker provides the execution layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting Simple: Containerizing a TypeScript Agent
&lt;/h3&gt;

&lt;p&gt;Let’s begin with a minimal example. Imagine a small TypeScript service that acts as an AI agent using an LLM API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@anthropic-ai/sdk&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;Anthropic&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-3-5-sonnet-20241022&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&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;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&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;prompt&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;Agent running on port 3000&lt;/span&gt;&lt;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 works locally, but we want to make it portable and reproducible. A multi-stage Dockerfile gives us a clean way to do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application runs the same way everywhere. There is no dependency drift, no missing tools, and no ambiguity about runtime behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving to Real Systems: Multi-Agent Architecture
&lt;/h3&gt;

&lt;p&gt;The real value of Docker becomes obvious when you move beyond a single service.&lt;/p&gt;

&lt;p&gt;Consider a common multi-agent setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A coordinator who receives requests&lt;/li&gt;
&lt;li&gt;A research agent that fetches and analyzes information&lt;/li&gt;
&lt;li&gt;A code agent that generates or modifies code&lt;/li&gt;
&lt;li&gt;Redis for communication&lt;/li&gt;
&lt;li&gt;PostgreSQL for persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of managing all of this manually, Docker Compose lets you define the entire system in one place.&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;coordinator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/coordinator&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://user:pass@postgres:5432/agents&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;research-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/research-agent&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3001"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;code-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/code-agent&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3002:3002"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=pass&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=agents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;docker-compose&lt;/code&gt; up brings the entire system to life. Each agent runs in isolation, but they communicate through well-defined channels. This is far more stable than trying to stitch together services manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  When TypeScript Meets Python
&lt;/h3&gt;

&lt;p&gt;Most AI systems today are not purely JavaScript. Even if your orchestration layer is TypeScript, you will likely depend on Python for embeddings, model execution, or specialized libraries.&lt;/p&gt;

&lt;p&gt;Docker makes this integration straightforward by separating concerns into services.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./agent-service&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ML_SERVICE_URL=http://ml-service:8000&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ml-service&lt;/span&gt;

  &lt;span class="na"&gt;ml-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ml-service&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your TypeScript agent can now call the Python service without worrying about local Python installations or dependency conflicts.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;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;http://ml-service:8000/embed&lt;/span&gt;&lt;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;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;text&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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation becomes critical as systems grow. Each service can scale independently, and each stack can evolve without breaking others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Experience Without Friction
&lt;/h3&gt;

&lt;p&gt;One concern developers often have is that Docker slows down iteration. That can happen if you rebuild containers on every change, but it does not have to.&lt;/p&gt;

&lt;p&gt;A better approach is to use volume mounts with a watch mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; tsx

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["tsx", "watch", "src/index.ts"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can edit code locally, and the container reloads automatically. You get the benefits of Docker without sacrificing developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Changes After Adopting This
&lt;/h3&gt;

&lt;p&gt;The impact of this approach is not theoretical. It shows up immediately in how teams work.&lt;/p&gt;

&lt;p&gt;Onboarding becomes faster because new developers do not need to recreate environments manually. Running the system becomes predictable because everything is defined in one place. Debugging improves because you eliminate environment-related variables.&lt;/p&gt;

&lt;p&gt;More importantly, it changes how you think about AI systems. Instead of treating them as scripts or services, you start treating them as controlled execution environments. The agent decides what to do, but Docker defines how it is allowed to do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A More Useful Mental Model
&lt;/h3&gt;

&lt;p&gt;It helps to think of modern AI systems as having three distinct layers.&lt;/p&gt;

&lt;p&gt;The first is the decision layer, where the language model or agent determines what actions to take. The second is the orchestration layer, typically written in TypeScript, where workflows and integrations are defined. The third is the execution layer, where those actions actually run.&lt;/p&gt;

&lt;p&gt;Docker fits naturally into that third layer. It provides a deterministic, isolated environment in which execution occurs safely and consistently.&lt;/p&gt;

&lt;p&gt;Once you start thinking in these terms, Docker no longer feels like an optional tool. It becomes a fundamental part of building reliable AI systems.&lt;/p&gt;

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

&lt;p&gt;The biggest mistake teams make with AI development today is underestimating the importance of the execution environment. It is easy to focus on prompts, models, and frameworks, but those are only part of the system.&lt;/p&gt;

&lt;p&gt;What matters just as much is where and how those decisions are executed.&lt;/p&gt;

&lt;p&gt;For TypeScript developers, Docker provides a practical way to bring structure and reliability to increasingly complex AI workflows. It bridges the gap between frontend development and distributed systems, without requiring a complete shift in tooling or mindset.&lt;/p&gt;

&lt;p&gt;If you are building AI agents in 2026, you are already working with multi-service systems, mixed runtimes, and non-deterministic behavior. Docker is what makes all of that manageable.&lt;/p&gt;

&lt;p&gt;Start small. Containerize a single agent. Then add services as your system grows. Over time, you will find that it is not just about making things run, but about making them run in a way you can trust.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>agents</category>
      <category>devops</category>
    </item>
    <item>
      <title>Cypress AI Skills: Teaching Your AI Assistant to Write Better Tests</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Thu, 09 Apr 2026 23:49:32 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/cypress-ai-skills-teaching-your-ai-assistant-to-write-better-tests-1dib</link>
      <guid>https://forem.com/raju_dandigam/cypress-ai-skills-teaching-your-ai-assistant-to-write-better-tests-1dib</guid>
      <description>&lt;p&gt;I’ve been using AI tools like Cursor and Claude Code to help write Cypress tests. It’s fast, and for simple cases, it works well enough.&lt;/p&gt;

&lt;p&gt;But as soon as you try to use it in a real project, the cracks start to show.&lt;/p&gt;

&lt;p&gt;You ask the AI to write a test, and you get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;.btn-primary&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="mi"&gt;3000&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;.modal-content .success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, technically. But it doesn’t look like something your team would ever commit.&lt;/p&gt;

&lt;p&gt;In most real-world projects, we avoid CSS selectors, we don’t rely on arbitrary waits, and we lean heavily on custom commands to keep tests maintainable. So instead of saving time, you end up rewriting most of what the AI generated.&lt;/p&gt;

&lt;p&gt;That’s the gap &lt;strong&gt;Cypress AI Skills&lt;/strong&gt; is trying to close.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Shift: From Code Generation to Code Alignment
&lt;/h3&gt;

&lt;p&gt;Most AI tools are good at generating code, but they don’t understand your codebase.&lt;/p&gt;

&lt;p&gt;They don’t know your selector strategy.&lt;br&gt;
They don’t know your custom commands.&lt;br&gt;
They don’t know your conventions or patterns.&lt;/p&gt;

&lt;p&gt;So they produce generic output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cypress AI Skills&lt;/strong&gt; introduces a simple but powerful idea: instead of asking AI to generate code, you teach it how your team writes tests.&lt;/p&gt;

&lt;p&gt;These skills live inside your project. They are not a service or a black box. Your AI assistant reads them, along with your existing code, and uses that context when generating or reviewing tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;The setup is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add cypress-io/ai-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, your AI assistant can use two key capabilities: &lt;code&gt;cypress-author&lt;/code&gt; and &lt;code&gt;cypress-explain&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Tests with &lt;code&gt;cypress-author&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The difference becomes obvious when you actually use the skill.&lt;/p&gt;

&lt;p&gt;Instead of a generic prompt, you explicitly guide the AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cypress-author Create a login test using existing custom commands and data-cy selectors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before generating anything, the AI looks into your project. It scans your Cypress configuration, your support files, and your custom commands.&lt;/p&gt;

&lt;p&gt;So instead of producing something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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@example.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;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;.login-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It generates something aligned with your actual setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authentication Flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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;logs in successfully and redirects to dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&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@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password123&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;password&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&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;getByCy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome-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;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not just cleaner code. The AI is now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusing your abstractions instead of duplicating logic&lt;/li&gt;
&lt;li&gt;Following your selector conventions&lt;/li&gt;
&lt;li&gt;Structuring tests the way your team already does&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where AI starts becoming useful in a real codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing and Improving Tests with &lt;code&gt;cypress-explain&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;cypress-author&lt;/code&gt; helps you write tests, &lt;code&gt;cypress-explain&lt;/code&gt; helps you improve them.&lt;/p&gt;

&lt;p&gt;You can take an existing test and ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/cypress-explain Review this &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;flakiness and bad practices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;.checkout-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&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;.confirmation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI doesn’t just describe the test. It evaluates how it’s written.&lt;/p&gt;

&lt;p&gt;It will point out things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliance on arbitrary waits&lt;/li&gt;
&lt;li&gt;Brittle selectors&lt;/li&gt;
&lt;li&gt;Missing synchronization with network calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it typically suggests a more stable version:&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;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/checkout&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;checkout&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;getByCy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;@checkout&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;getByCy&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-confirmation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, this feels less like code generation and more like having a second reviewer who understands Cypress-specific pitfalls.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Changes in a Real Project
&lt;/h3&gt;

&lt;p&gt;One of the most useful ways to think about this is through transformation.&lt;/p&gt;

&lt;p&gt;Without context, AI writes tests that are technically valid but fragile:&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;.login-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&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;.dashboard&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;With Cypress AI Skills, the same intent becomes:&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;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/login&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;loginRequest&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;getByCy&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-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;@loginRequest&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;getByCy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shift is subtle but meaningful. The test becomes more stable, easier to read, and consistent with the rest of your suite.&lt;/p&gt;

&lt;p&gt;This is not about writing faster tests. It’s about writing tests that don’t need to be rewritten later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where This Helps the Most
&lt;/h3&gt;

&lt;p&gt;In real projects, the value shows up in a few consistent places.&lt;/p&gt;

&lt;p&gt;When refactoring older tests, the AI can take brittle patterns and align them with your current standards. Instead of manually fixing selectors and waits across files, you can guide the transformation with a prompt.&lt;/p&gt;

&lt;p&gt;When generating tests from requirements, the AI produces code that already fits your project structure, including your helpers and naming conventions.&lt;/p&gt;

&lt;p&gt;When debugging flaky tests, &lt;code&gt;cypress-explain&lt;/code&gt; helps quickly identify the root cause, especially around timing issues and missing intercepts.&lt;/p&gt;

&lt;p&gt;It also becomes surprisingly useful for onboarding. New engineers can ask the AI to explain how tests are structured or how certain flows are implemented, and the answers are grounded in your actual codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note on &lt;code&gt;cy.prompt()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There is also an emerging pattern around intent-based testing using cy.prompt().&lt;/p&gt;

&lt;p&gt;Instead of targeting elements directly, you describe the action:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cy.prompt('Navigate to the final payment screen')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The idea is to express intent rather than implementation.&lt;/p&gt;

&lt;p&gt;This can be useful in cases where the UI changes frequently or when working with third-party components where selectors are unstable.&lt;/p&gt;

&lt;p&gt;That said, this approach is still evolving. It introduces variability and can be slower than traditional tests, so it’s better suited for exploratory or non-critical flows rather than core test paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making This Work for Your Team
&lt;/h3&gt;

&lt;p&gt;For Cypress AI Skills to be effective, a few foundational things matter.&lt;/p&gt;

&lt;p&gt;Your project should have clear conventions. If your selectors and patterns are inconsistent, the AI won’t have a strong signal to follow.&lt;/p&gt;

&lt;p&gt;It also helps to make those conventions visible. Even a simple document describing your testing approach can improve the quality of AI output.&lt;/p&gt;

&lt;p&gt;One important practice is to treat these skills as part of your codebase. Commit them, version them, and let your entire team benefit from the same behavior. This ensures that AI-assisted code remains consistent regardless of who generates it.&lt;/p&gt;

&lt;p&gt;And like any generated code, it still needs review. The goal is not to remove that step, but to make it faster and more focused.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Is (and What It’s Not)
&lt;/h3&gt;

&lt;p&gt;Cypress AI Skills are not a solution for fully automated testing. They don’t magically fix flaky tests, and they don’t replace the need for thoughtful test design.&lt;/p&gt;

&lt;p&gt;What they do is much more practical.&lt;/p&gt;

&lt;p&gt;They reduce the gap between what AI generates and what your team expects.&lt;/p&gt;

&lt;p&gt;Instead of treating AI as a generic code generator, you start treating it as a project-aware assistant.&lt;/p&gt;

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

&lt;p&gt;AI is already changing how we write code, but testing has been slower to benefit because generic output rarely aligns with real-world practices.&lt;/p&gt;

&lt;p&gt;Cypress AI Skills takes a meaningful step forward by making AI aware of your project, your conventions, and your workflows.&lt;/p&gt;

&lt;p&gt;It’s not a dramatic shift, but it’s a practical one. And in day-to-day engineering, those are the improvements that actually stick.&lt;/p&gt;

&lt;p&gt;If you’re already using AI tools with Cypress, this small change can make a noticeable difference.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://docs.cypress.io/app/tooling/ai-skills" rel="noopener noreferrer"&gt;Cypress AI Skill&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cypress</category>
      <category>claude</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Docker as the Sandbox for AI Agents: Safe Cypress Workflows for Frontend Teams</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:37:23 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/docker-as-the-sandbox-for-ai-agents-safe-cypress-workflows-for-frontend-teams-2h74</link>
      <guid>https://forem.com/raju_dandigam/docker-as-the-sandbox-for-ai-agents-safe-cypress-workflows-for-frontend-teams-2h74</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;A Small Mistake That Breaks Everything. An agent tries to fix a failing test. It sees a permission error and decides to help. It runs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chmod -R 777 .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, your project folder is symlinked to a broader directory. Within seconds, your local environment is exposed, permissions are broken, and debugging becomes a nightmare.&lt;/p&gt;

&lt;p&gt;This is not a far-fetched scenario. It is the natural outcome of giving an autonomous system unrestricted access to your machine.&lt;/p&gt;

&lt;p&gt;AI-assisted development has evolved quickly. Agents can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Modify files&lt;/li&gt;
&lt;li&gt;Execute scripts&lt;/li&gt;
&lt;li&gt;Propose pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With tools and protocols like MCP (Model Context Protocol), they are no longer passive assistants. They are active participants in your development workflow.&lt;/p&gt;

&lt;p&gt;And that raises a fundamental question: Should AI agents have direct access to your development environment at all?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with Host-Based Agent Execution
&lt;/h3&gt;

&lt;p&gt;In most current setups, the execution model looks 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%2Fsjl3g6if747f1xdtpm7u.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%2Fsjl3g6if747f1xdtpm7u.png" alt=" " width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach is convenient, but it creates systemic issues.&lt;/p&gt;

&lt;p&gt;First, there is &lt;strong&gt;security exposure&lt;/strong&gt;. The agent can access environment variables, local files, SSH keys, and system-level resources. Even if the agent behaves correctly, the risk surface is too large.&lt;/p&gt;

&lt;p&gt;Second, there is &lt;strong&gt;environment inconsistency&lt;/strong&gt;. The agent’s behavior depends on the local machine—Node versions, OS differences, and installed dependencies. The result is a new variation of the classic problem: “it works on my machine,” but now applied to AI workflows.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;reproducibility breaks down&lt;/strong&gt;. When an agent executes tasks in an uncontrolled environment, it becomes difficult to recreate the same conditions elsewhere, particularly in CI.&lt;/p&gt;

&lt;p&gt;Finally, debugging becomes complicated because there is no clear boundary between the agent’s actions and the local system state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rethinking the Model: Agents Need Environments, Not Access
&lt;/h3&gt;

&lt;p&gt;We need to stop treating AI agents like supercharged IDE plugins and start treating them like untrusted third-party binaries.&lt;/p&gt;

&lt;p&gt;That shift leads to a different execution model:&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%2Fegq7yhajt2043gti4bzu.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%2Fegq7yhajt2043gti4bzu.png" alt=" " width="635" height="1396"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The agent does not execute directly on your machine; it interacts with a containerized environment, and all operations happen within a controlled boundary.&lt;/p&gt;

&lt;p&gt;The key difference is subtle but critical: "We are no longer giving the agent access. We are giving it an environment."&lt;/p&gt;

&lt;h3&gt;
  
  
  Where MCP Fits: The Container as the “Jailer”
&lt;/h3&gt;

&lt;p&gt;Earlier, we mentioned MCP (Model Context Protocol). This is where it becomes essential.&lt;/p&gt;

&lt;p&gt;In most setups, MCP tools allow agents to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read files&lt;/li&gt;
&lt;li&gt;Run commands&lt;/li&gt;
&lt;li&gt;Inspect repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If MCP is connected directly to your host machine, it becomes a gateway to your entire system. Instead, the MCP server should run inside the container.&lt;/p&gt;

&lt;p&gt;This changes the architecture:&lt;/p&gt;

&lt;p&gt;The agent communicates with the MCP and operates within the container boundary. All file access and command execution are scoped to that environment&lt;/p&gt;

&lt;p&gt;Effectively, MCP becomes the interface, and the container becomes the jailer that enforces constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Practical Example: Fixing a Cypress Test
&lt;/h3&gt;

&lt;p&gt;Let’s walk through a real-world scenario.&lt;/p&gt;

&lt;p&gt;A Cypress test fails in CI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Error: Expected to find element:&lt;br&gt;
[data-testid="submit-btn"]&lt;br&gt;
But never found it.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;An AI agent analyzes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The failing test&lt;/li&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Possibly a DOM snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It identifies a selector change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- cy.get('[data-testid="submit-btn"]')
&lt;/span&gt;&lt;span class="gi"&gt;+ cy.get('[data-testid="submit-button"]')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deterministic Execution and Binary Parity
&lt;/h3&gt;

&lt;p&gt;Here’s where things often go wrong in typical setups. The failure occurred in CI, which likely runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;A specific Node version&lt;/li&gt;
&lt;li&gt;A specific Cypress binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the agent might attempt to validate the fix on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;ARM architecture&lt;/li&gt;
&lt;li&gt;A different Node version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mismatch leads to incorrect conclusions or “hallucinated fixes.” To avoid this, the agent executes inside a container that matches the CI environment:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run --rm \&lt;br&gt;
  -v $(pwd):/app \&lt;br&gt;
  -w /app \&lt;br&gt;
  cypress/included:13.6.0 \&lt;br&gt;
  npx cypress run&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This ensures binary parity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same OS&lt;/li&gt;
&lt;li&gt;Same dependencies&lt;/li&gt;
&lt;li&gt;Same runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent is now debugging the exact environment where the failure occurred.&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%2Fd6on2n3q50ajtebcyhfx.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%2Fd6on2n3q50ajtebcyhfx.png" alt=" " width="800" height="1684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This workflow ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation happens in a controlled environment&lt;/li&gt;
&lt;li&gt;The results are reproducible&lt;/li&gt;
&lt;li&gt;Fixes are grounded in real execution, not assumptions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Hybrid Strategy with Docker Compose
&lt;/h3&gt;

&lt;p&gt;To make this practical, you can structure your setup like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Service A (Host-facing)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite dev server&lt;/li&gt;
&lt;li&gt;Ports exposed&lt;/li&gt;
&lt;li&gt;Optimized for speed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service B (Agent Sandbox)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No exposed ports&lt;/li&gt;
&lt;li&gt;Volume-mounted code&lt;/li&gt;
&lt;li&gt;Runs tests and agent workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example concept:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run dev&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5173:5173"&lt;/span&gt;

  &lt;span class="na"&gt;agent-sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress/included:13.6.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cypress run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trade-offs and Considerations
&lt;/h3&gt;

&lt;p&gt;This architecture introduces additional complexity. Containers need to be configured and maintained. There is some overhead in startup time and resource usage.&lt;/p&gt;

&lt;p&gt;However, these costs are offset by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved security boundaries&lt;/li&gt;
&lt;li&gt;Deterministic execution&lt;/li&gt;
&lt;li&gt;Reproducible debugging&lt;/li&gt;
&lt;li&gt;Safer integration of AI agents into workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams operating at scale, these benefits become essential rather than optional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where This Is Heading
&lt;/h3&gt;

&lt;p&gt;As AI agents become more capable, they will move from assisting developers to executing entire workflows. At that point, the key question is no longer:&lt;br&gt;
“What can the agent do?”&lt;br&gt;
It becomes:&lt;br&gt;
“Where is the agent allowed to do it?”&lt;br&gt;
In this context, Docker evolves from a deployment tool into an execution layer for intelligence.&lt;/p&gt;

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

&lt;p&gt;AI agents introduce a powerful new capability into frontend development, but they also require a shift in how we think about execution and trust.&lt;/p&gt;

&lt;p&gt;Allowing agents to operate directly on the host machine is convenient, but it is not a sustainable model for teams that prioritize security, consistency, and reproducibility.&lt;/p&gt;

&lt;p&gt;By introducing containerized execution, we move from access-based thinking to environment-based thinking. This creates a safer and more predictable foundation for integrating AI into development workflows.&lt;/p&gt;

&lt;p&gt;The future of frontend development will not be defined solely by faster tools, but by how effectively we control and constrain the systems that use them.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI-Assisted Cypress CI: Detecting Selector Drift and Proposing Fixes Automatically</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Sun, 15 Mar 2026 05:55:20 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/ai-assisted-cypress-ci-detecting-selector-drift-and-proposing-fixes-automatically-pg4</link>
      <guid>https://forem.com/raju_dandigam/ai-assisted-cypress-ci-detecting-selector-drift-and-proposing-fixes-automatically-pg4</guid>
      <description>&lt;p&gt;CI failures are frustrating — especially when the fix is obvious.&lt;/p&gt;

&lt;p&gt;A very common scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cypress tests pass locally&lt;/li&gt;
&lt;li&gt;CI fails overnight&lt;/li&gt;
&lt;li&gt;The screenshot clearly shows that the UI has changed&lt;/li&gt;
&lt;li&gt;The test selector is outdated&lt;/li&gt;
&lt;li&gt;Someone has to open logs and manually update the test&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;code&gt;CypressError: Timed out retrying: Expected to find element:&lt;br&gt;
[data-testid="submit-profile-btn"], but never found it.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you open the screenshot, the button is clearly visible, but now the selector is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data-testid="save-profile-btn"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This type of issue is selector drift. It happens when the UI changes slightly, but tests still reference the old selector. The fix usually takes seconds, but the debugging process is repetitive. In this article, we will build a small AI-assisted CI workflow that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Runs Cypress tests in GitHub Actions&lt;/li&gt;
&lt;li&gt;Captures failure artifacts&lt;/li&gt;
&lt;li&gt;Sends failure context to an LLM&lt;/li&gt;
&lt;li&gt;The LLM detects possible selector drift&lt;/li&gt;
&lt;li&gt;A PR. comment suggests the patch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not autonomous self-healing CI. Instead, it’s a supervised AI workflow that helps developers fix simple test failures faster.&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cypress test fails
        ↓
GitHub Actions captures failure artifacts
        ↓
DOM snapshot + error message sent to LLM
        ↓
LLM analyzes failure
        ↓
PR comment suggests selector fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers remain in control and review the suggestion.&lt;/p&gt;

&lt;p&gt;Demo App:&lt;br&gt;
For this example we use a tiny React component.&lt;br&gt;
&lt;code&gt;src/ProfilePage.jsx&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfilePage&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;handleSave&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="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Profile updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&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;testid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* UI change happened here */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&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;testid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save-profile-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSave&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;Save&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Originally, the button used: &lt;code&gt;submit-profile-btn&lt;/code&gt;&lt;br&gt;
After a UI update, it became: &lt;code&gt;save-profile-btn&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1 — Write the Cypress Test
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cypress/e2e/profile.cy.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="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="s2"&gt;Profile page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updates profile&lt;/span&gt;&lt;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="s2"&gt;/profile&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-testid="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="s2"&gt;Test User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Profile updated&lt;/span&gt;&lt;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;Once the selector changes, the test fails in CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Capture Failure Context
&lt;/h3&gt;

&lt;p&gt;We capture useful context when tests fail.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cypress/support/e2e.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="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTest&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="s2"&gt;failed&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;document&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;doc&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;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outerHTML&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;saveFailureContext&lt;/span&gt;&lt;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;testTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTest&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="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;html&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;cypress.config.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="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;e2e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="s2"&gt;task&lt;/span&gt;&lt;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;saveFailureContext&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="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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts&lt;/span&gt;&lt;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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts&lt;/span&gt;&lt;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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts/failure.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;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;data&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="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when a test fails, we capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test title&lt;/li&gt;
&lt;li&gt;Cypress error message&lt;/li&gt;
&lt;li&gt;page URL&lt;/li&gt;
&lt;li&gt;DOM snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3 — Run Cypress in GitHub Actions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/cypress.yml&lt;/code&gt;&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 CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&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;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;ubuntu-latest&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;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="pi"&gt;-&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/setup-node@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;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;

      &lt;span class="pi"&gt;-&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 install&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&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;cypress-io/github-action@v6&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;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm start&lt;/span&gt;
          &lt;span class="na"&gt;wait-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000'&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 failure artifacts&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;failure()&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;failure-context&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;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;Analyze failure&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;failure()&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 scripts/analyze-failure.js&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;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&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;Comment on PR&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;failure() &amp;amp;&amp;amp; github.event_name == 'pull_request'&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@v8&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;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&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 fs = require('fs')&lt;/span&gt;

            &lt;span class="s"&gt;if (!fs.existsSync('artifacts/comment.md')) return&lt;/span&gt;

            &lt;span class="s"&gt;const body = fs.readFileSync('artifacts/comment.md','utf8')&lt;/span&gt;

            &lt;span class="s"&gt;await github.rest.issues.createComment({&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;issue_number: context.issue.number,&lt;/span&gt;
              &lt;span class="s"&gt;body&lt;/span&gt;
            &lt;span class="s"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Sanitize the DOM Snapshot
&lt;/h3&gt;

&lt;p&gt;Large React apps can produce massive DOM trees. We would need to clean the HTML before sending it to the LLM.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scripts/analyze-failure.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;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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;html&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;svg&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;svg&amp;gt;/g&lt;/span&gt;&lt;span class="p"&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;style&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;style&amp;gt;/g&lt;/span&gt;&lt;span class="p"&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;script&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;script&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes large SVG blocks, style tags, and scripts while keeping the useful structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Scrub Sensitive Data
&lt;/h3&gt;

&lt;p&gt;Before sending HTML to an LLM, we should remove PII.&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;function&lt;/span&gt; &lt;span class="nf"&gt;scrubPII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9._%+-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9.-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,}&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[EMAIL]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then combine both steps:&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;cleanedDOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrubPII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;sanitizeDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 — Call the LLM
&lt;/h3&gt;

&lt;p&gt;Now we analyze the failure using an LLM.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scripts/analyze-failure.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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts/failure.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="s2"&gt;No failure data&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;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;failure&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts/failure.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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;html&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;svg&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;svg&amp;gt;/g&lt;/span&gt;&lt;span class="p"&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;style&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;style&amp;gt;/g&lt;/span&gt;&lt;span class="p"&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;script&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;script&amp;gt;/g&lt;/span&gt;&lt;span class="p"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scrubPII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9._%+-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9.-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,}&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[EMAIL]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanedDOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrubPII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;sanitizeDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20000&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are a senior Cypress reliability engineer.

Classify this test failure.

Possible classifications:

selector-drift
async-loading
logic-error
unknown

Only suggest a selector fix if classification = selector-drift.

Failure message:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

DOM snapshot:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cleanedDOM&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

Return JSON:

{
"classification":"",
"suggested_selector":"",
"confidence":"",
"reason":""
}
`&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You analyze Cypress failures.&lt;/span&gt;&lt;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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts/analysis.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;result&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;parsed&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;result&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;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classification&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selector-drift&lt;/span&gt;&lt;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;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="s2"&gt;No selector drift detected&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;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;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
### AI Test Suggestion

Possible selector drift detected.

Suggested selector:
&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;suggested_selector&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;

Suggested patch:

&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;diff
- cy.get('[data-testid="submit-profile-btn"]')
+ cy.get('[data-testid="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;suggested_selector&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]')
&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;

Confidence: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

Reason: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

Please review before applying.
`&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifacts/comment.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When CI fails, the PR receives something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;AI Test Suggestion

Possible selector drift detected.

Suggested selector:
save-profile-btn

Suggested patch:
&lt;span class="p"&gt;
-&lt;/span&gt; cy.get('[data-testid="submit-profile-btn"]')
&lt;span class="p"&gt;+&lt;/span&gt; cy.get('[data-testid="save-profile-btn"]')

Confidence: 0.91
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can quickly review and update the test.&lt;/p&gt;

&lt;h3&gt;
  
  
  When This Approach Works Best
&lt;/h3&gt;

&lt;p&gt;This AI-assisted workflow works best in projects that follow a few good testing practices. In particular, it performs well when tests use stable selectors such as data-testid or data-cy, UI changes are relatively small, and the application behaves predictably across environments. In these cases, the AI can often detect simple selector drift caused by renamed attributes, minor DOM structure changes, or small UI updates.&lt;/p&gt;

&lt;p&gt;At the same time, it’s important to remember that not every Cypress failure is caused by selector drift. Test failures can also occur due to network timing issues, missing test data, authentication problems, loading states, or feature flags. Because of this, the workflow is designed to suggest fixes rather than apply them automatically, ensuring that developers can review the recommendation before updating the test.&lt;/p&gt;

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

&lt;p&gt;Many CI failures require only a small selector update, but developers still spend time investigating logs and screenshots to find the issue. By adding a lightweight AI analyzer to the CI pipeline, teams can automatically detect possible selector drift and surface suggested fixes directly in pull requests.&lt;/p&gt;

&lt;p&gt;This approach does not replace developers or fully automate test maintenance. Instead, it reduces repetitive debugging work and shortens the feedback loop when UI changes break tests. With just a few scripts and a GitHub Action, teams can make their Cypress CI pipeline smarter and spend more time focusing on real product improvements.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>cypress</category>
      <category>webdev</category>
    </item>
    <item>
      <title>8 Cypress Plugins Shaping Modern Testing in 2025</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Wed, 23 Jul 2025 02:41:47 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/8-cypress-plugins-shaping-modern-testing-in-2025-6cb</link>
      <guid>https://forem.com/raju_dandigam/8-cypress-plugins-shaping-modern-testing-in-2025-6cb</guid>
      <description>&lt;p&gt;Cypress continues to dominate the web testing ecosystem in 2025. Its plugin ecosystem has matured and now plays a vital role in enabling high-quality, scalable, and developer-friendly testing pipelines.&lt;/p&gt;

&lt;p&gt;Here are 8 Cypress plugins that have stood out this year—based on adoption, developer feedback, and impact on modern testing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;eslint-plugin-cypress&lt;/code&gt;: Enforcing Cypress Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;This plugin enforces Cypress-specific linting rules to catch anti-patterns like misuse of async/await, unnecessary waits, and missing assertions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev eslint-plugin-cypress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// eslint.config.js
import pluginCypress from 'eslint-plugin-cypress';
export default [pluginCypress.configs.recommended];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Prevents flaky tests
&lt;/li&gt;
&lt;li&gt;Works with ESLint v9+
&lt;/li&gt;
&lt;li&gt;Cypress core team recommendation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;cypress-real-events&lt;/code&gt;: Simulate True User Behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Simulates native browser events like &lt;code&gt;hover&lt;/code&gt;, &lt;code&gt;tab&lt;/code&gt;, and &lt;code&gt;realClick&lt;/code&gt; that synthetic events miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev cypress-real-events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'cypress-real-events';
cy.get('button').realClick();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Mimics real user actions
&lt;/li&gt;
&lt;li&gt;Great for UIs involving hover, tab, modals
&lt;/li&gt;
&lt;li&gt;Chromium-based support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;@cypress/code-coverage&lt;/code&gt;: Know What You're Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Helps you visualize what code paths are actually covered by your tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @cypress/code-coverage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import codeCoverageTask from '@cypress/code-coverage/task';

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      codeCoverageTask(on, config);
      return config;
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Works with component &amp;amp; E2E tests
&lt;/li&gt;
&lt;li&gt;Istanbul support
&lt;/li&gt;
&lt;li&gt;LCOV and HTML reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;@testing-library/cypress&lt;/code&gt;: User-Focused Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Provides queries like &lt;code&gt;getByRole&lt;/code&gt;, &lt;code&gt;findByText&lt;/code&gt; aligned with user interaction patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @testing-library/cypress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '@testing-library/cypress/add-commands';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.findByRole('button', { name: /submit/i }).click();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Encourages accessible and maintainable tests
&lt;/li&gt;
&lt;li&gt;Framework-agnostic
&lt;/li&gt;
&lt;li&gt;Enhances readability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;cypress-axe&lt;/code&gt;: Automated Accessibility Checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Brings automated WCAG audits into your CI pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev cypress-axe axe-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.injectAxe();
cy.checkA11y();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Catches a11y issues early
&lt;/li&gt;
&lt;li&gt;Supports custom axe rules
&lt;/li&gt;
&lt;li&gt;Works with CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. &lt;code&gt;cypress-vite&lt;/code&gt;: Use Your Vite Config
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Improves alignment and performance for Vite-powered apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D cypress-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'cypress';
import { vitePreprocessor } from 'cypress-vite';

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('file:preprocessor', vitePreprocessor());
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Faster builds
&lt;/li&gt;
&lt;li&gt;Aligns app and test config
&lt;/li&gt;
&lt;li&gt;Native Vite support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. &lt;code&gt;cypress-mochawesome-reporter&lt;/code&gt;: Better Test Reports
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Creates rich HTML reports with screenshots and logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D cypress-mochawesome-reporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reporter: 'cypress-mochawesome-reporter'
import 'cypress-mochawesome-reporter/register';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Helpful for non-dev stakeholders
&lt;/li&gt;
&lt;li&gt;Works with CI pipelines
&lt;/li&gt;
&lt;li&gt;Generates HTML + JSON output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. &lt;code&gt;@cypress/grep&lt;/code&gt;: Advanced Filtering With Tags
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;Lets you run or skip tests by tags, beyond what &lt;code&gt;--spec&lt;/code&gt; allows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D @cypress/grep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx cypress run --env grepTags=@smoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it('works correctly @smoke', () =&amp;gt; {...});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Enables modular test runs
&lt;/li&gt;
&lt;li&gt;Useful for CI workflows
&lt;/li&gt;
&lt;li&gt;Tag-based control&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Cypress plugins continue to raise the bar for end-to-end and component testing. These 8 tools can help you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve test coverage&lt;/li&gt;
&lt;li&gt;Speed up local + CI runs&lt;/li&gt;
&lt;li&gt;Catch accessibility gaps&lt;/li&gt;
&lt;li&gt;Write maintainable tests your team loves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which plugin do you rely on most? Let me know in the comments 👇&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>webdev</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Running ML Models Locally with Docker Model Runner</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Wed, 02 Jul 2025 02:29:40 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/running-ml-models-locally-with-docker-model-runner-2pio</link>
      <guid>https://forem.com/raju_dandigam/running-ml-models-locally-with-docker-model-runner-2pio</guid>
      <description>&lt;p&gt;Docker Model Runner is designed to make running AI and ML models locally as easy as running any Docker service. It lets you package trained models as containers with consistent REST APIs—no custom server code required. In this guide, we’ll cover everything you need to know to use Docker Model Runner in real-world development workflows, including how to run models locally, configure Docker Desktop, connect from Node.js apps, use Docker Compose for orchestration, and follow best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is Docker Model Runner?
&lt;/h3&gt;

&lt;p&gt;Docker Model Runner lets you package your trained model with metadata that tells Docker how to serve it. When you run the resulting image, you get a standardized REST API automatically, with endpoints like /predict and /health. This eliminates the need to write and maintain your own serving code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use It?
&lt;/h3&gt;

&lt;p&gt;Traditionally, serving ML models required custom web servers, complex dependency management, and inconsistent APIs across teams. Docker Model Runner solves this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Providing consistent APIs across all models.&lt;/li&gt;
&lt;li&gt;Simplifying local development.&lt;/li&gt;
&lt;li&gt;Making models portable across machines and environments.&lt;/li&gt;
&lt;li&gt;Reducing maintenance by removing custom server code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Supported Frameworks
&lt;/h3&gt;

&lt;p&gt;Docker Model Runner supports a wide range of frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PyTorch&lt;/li&gt;
&lt;li&gt;TensorFlow&lt;/li&gt;
&lt;li&gt;Hugging Face Transformers&lt;/li&gt;
&lt;li&gt;scikit-learn&lt;/li&gt;
&lt;li&gt;XGBoost&lt;/li&gt;
&lt;li&gt;LightGBM&lt;/li&gt;
&lt;li&gt;spaCy&lt;/li&gt;
&lt;li&gt;ONNX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can use the same approach for a huge variety of ML workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works in Practice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Train your model.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; Write a &lt;code&gt;model-runner.yaml&lt;/code&gt; describing the framework and location of your model.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; Build your Docker image with this metadata and your model files.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; Run the container and get a consistent REST API without writing server code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running Models Locally with Docker Model Runner
&lt;/h3&gt;

&lt;p&gt;Below is a real example using the ai/smollm2:latest model running locally. This demonstrates how easy it is to list available models and start a local interactive chat session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull the model:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker model pull ai/smollm2:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View the models available:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker model list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the model:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker model run ai/smollm2:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’ll get an interactive chat session where you can type questions directly to the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give the prompt:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;What is Docker?&lt;/code&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%2F0yxvbh2k6er01hv6ljd2.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%2F0yxvbh2k6er01hv6ljd2.png" alt="Docker model running locally" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Docker Desktop Settings for Local Model Running
&lt;/h3&gt;

&lt;p&gt;Need to update the settings in Docker Desktop. Allow TCP host connections for Model Runner via Docker Desktop settings or using CLI options for advanced networking control.&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%2F15x0zlo5gc3l8sfzhy2f.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%2F15x0zlo5gc3l8sfzhy2f.png" alt="Docker desktop settings" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker Desktop makes it even easier to manage these models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Models&lt;/strong&gt; tab in Docker Desktop.&lt;/li&gt;
&lt;li&gt;Browse and manage available local models.&lt;/li&gt;
&lt;li&gt;Launch interactive chat interfaces directly from the UI.&lt;/li&gt;
&lt;li&gt;Monitor container resource usage and logs.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You can adjust resource allocation in Settings → Resources, making sure your local environment has enough CPU and memory to handle larger models.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using Docker Models in a Node.js App
&lt;/h3&gt;

&lt;p&gt;You can use Docker Model Runner locally with any language. Here’s how you’d connect to your local model from a simple Node.js app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Express.js Route:&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;app.post('/generate', async (req, res) =&amp;gt; {
  const prompt = req.body.prompt;
  const response = await fetch('http://localhost:5000/predict', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt })
  });
  const data = await response.json();
  res.send(data);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app code never changes if you swap models.&lt;/li&gt;
&lt;li&gt;You can test locally and later deploy the same model container in production.&lt;/li&gt;
&lt;li&gt;Changing models is as simple as changing the running container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using Docker Compose for Multi-Model Pipelines
&lt;/h3&gt;

&lt;p&gt;You can chain multiple Model Runner services using Docker Compose to build advanced workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Use Case: Content Moderation Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toxicity Detection MCP → Check user input.&lt;/li&gt;
&lt;li&gt;Language Detection MCP → Identify language.&lt;/li&gt;
&lt;li&gt;Translation MCP → Normalize to English.&lt;/li&gt;
&lt;li&gt;Summarization MCP → Condense for storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;docker-compose.yml Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.8"
services:
  toxicity-detector:
    image: myorg/toxicity-mcp
    ports:
      - "5001:80"
  language-detector:
    image: myorg/langdetect-mcp
    ports:
      - "5002:80"
  translator:
    image: myorg/translator-mcp
    ports:
      - "5003:80"
  summarizer:
    image: myorg/summarizer-mcp
    ports:
      - "5004:80"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now your app can call each service in sequence for a complete moderation and summarization pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define your model-runner.yaml in the repo.&lt;/li&gt;
&lt;li&gt;Build Docker images in CI pipelines.&lt;/li&gt;
&lt;li&gt;Tag images with version numbers or commit SHAs.&lt;/li&gt;
&lt;li&gt;Run Docker Scout or other scanners for CVEs.&lt;/li&gt;
&lt;li&gt;Push images to internal or external registries.&lt;/li&gt;
&lt;li&gt;Deploy using Compose, Swarm, or Kubernetes.&lt;/li&gt;
&lt;li&gt;Include automated health checks against /health.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures your model deployment is as maintainable and secure as any other Microservice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Always define clear input/output contracts.&lt;/li&gt;
&lt;li&gt;Use private registries for internal or proprietary models.&lt;/li&gt;
&lt;li&gt;Tag images with semantic versions.&lt;/li&gt;
&lt;li&gt;Scan images regularly for vulnerabilities.&lt;/li&gt;
&lt;li&gt;Keep model-runner.yaml under version control.&lt;/li&gt;
&lt;li&gt;Automate builds and deployments via CI/CD.&lt;/li&gt;
&lt;li&gt;Use resource limits in Docker Desktop settings to avoid overloading local environments.&lt;/li&gt;
&lt;li&gt;Document how to call /predict and interpret results for consuming teams.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Docker Model Runner isn't just a convenience tool—it's a shift in how teams can think about model serving. Instead of building and maintaining custom servers for every model, you get standardization, portability, and repeatability. Whether you’re running a small LLM locally for testing, deploying to production Kubernetes clusters, or sharing images across teams, Docker Model Runner makes model serving a first-class, manageable part of your software architecture.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>model</category>
      <category>docker</category>
      <category>ai</category>
    </item>
    <item>
      <title>Docker MCP Servers: Standardizing AI/ML Workflows for the Agentic Future</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Wed, 02 Jul 2025 00:35:56 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/docker-mcp-servers-standardizing-aiml-workflows-for-the-agentic-future-1bf4</link>
      <guid>https://forem.com/raju_dandigam/docker-mcp-servers-standardizing-aiml-workflows-for-the-agentic-future-1bf4</guid>
      <description>&lt;p&gt;AI and ML are transforming applications across industries, but deploying models at scale is challenging. Docker MCP (Model Context Protocol) solves this by defining a standard way to package, deploy, and manage AI/ML models as secure, discoverable, and portable Docker containers. This ensures consistent APIs, standardized metadata, and seamless integration into microservice architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Docker MCP?
&lt;/h3&gt;

&lt;p&gt;Docker MCP is a framework for packaging AI/ML models as Docker images with standardized HTTP APIs and clear metadata. Each MCP server can run anywhere Docker runs—from developer laptops to cloud clusters and edge devices. It offers predictable, versioned endpoints for easy consumption by other services or applications. Metadata includes supported operations, input/output formats, and even sample payloads, enabling seamless discovery and reuse through the &lt;strong&gt;Docker Hub MCP Catalog&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This standardization also enables enterprises to manage dependencies cleanly, enforce security policies, and ensure repeatable, consistent deployment workflows across environments.&lt;/p&gt;

&lt;p&gt;Example usage:&lt;br&gt;
&lt;code&gt;docker run myorg/sentiment-mcp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Call the service:&lt;br&gt;
&lt;code&gt;curl -X POST localhost:5000/analyze -d '{"text":"I like this!"}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response:&lt;br&gt;
&lt;code&gt;{"sentiment":"positive"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why MCP Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without MCP, organizations often struggle with inconsistent APIs across teams, fragile integrations, and duplicated effort. Each ML team might build custom wrappers with different formats and dependency requirements. This leads to fragile glue code and slower time to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With MCP:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard APIs&lt;/strong&gt; make integration predictable and reduce onboarding time for new services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable images&lt;/strong&gt; can be deployed on any infrastructure that supports Docker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverable catalog&lt;/strong&gt; enables easy reuse of existing services and models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security features&lt;/strong&gt; like image signing and vulnerability scanning with Docker Scout help enforce enterprise policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning and tagging&lt;/strong&gt; ensure traceability and rollback capabilities in CI/CD pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP solves these challenges by making AI services composable, consistent, and secure by default.&lt;/p&gt;
&lt;h3&gt;
  
  
  Docker MCP Catalog and Toolkit
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;A rapidly growing library of verified MCP images, including official and community-contributed models.&lt;/li&gt;
&lt;li&gt;Metadata that describes the expected endpoints, supported operations, and sample inputs/outputs.&lt;/li&gt;
&lt;li&gt;Production-ready examples include Anthropic Claude MCP for advanced text generation, Mistral 7B MCP for local inference, Sentence Transformers MCP for semantic search, and Stable Diffusion MCP for image generation.&lt;/li&gt;
&lt;li&gt;Enables teams to easily find and evaluate models for their workflows, reducing duplication and fostering internal standards.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Docker Desktop extension and CLI that streamlines deployment of MCP servers.&lt;/li&gt;
&lt;li&gt;One-click deployment with secure credential handling and OAuth integration.&lt;/li&gt;
&lt;li&gt;Resource isolation for CPU, memory, and storage to ensure predictable performance.&lt;/li&gt;
&lt;li&gt;Provides gateways for popular development environments such as VS Code or local notebooks.&lt;/li&gt;
&lt;li&gt;Simplifies onboarding for data science and engineering teams, encouraging self-service deployment of AI services.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Example Workflow: Support Ticket Agent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automate triage of customer support messages by transforming unstructured text into structured, actionable tickets.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sentiment MCP&lt;/strong&gt; – Analyze the emotional tone to determine urgency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keywords MCP&lt;/strong&gt; – Extract product names, locations, or relevant entities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarizer MCP&lt;/strong&gt; – Condense the customer's message into a clear description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JIRA Creator MCP&lt;/strong&gt; – Automatically create a structured ticket in your issue tracker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Compose Example:&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;version: "3.8"
services:
  sentiment:
    image: myorg/sentiment-mcp
  keywords:
    image: myorg/keywords-mcp
  summarizer:
    image: myorg/summarizer-mcp
  jira:
    image: myorg/jira-creator-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teams can prototype this locally using &lt;code&gt;docker-compose up&lt;/code&gt; and seamlessly scale the same configuration to production using Swarm or Kubernetes, maintaining consistent behavior across environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Agentic AI Systems with MCP
&lt;/h3&gt;

&lt;p&gt;Modern AI agents require the ability to plan, retrieve, reason, and act through dynamic workflows. MCP supports these patterns by defining composable, discoverable services with consistent APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Agentic Workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NLP MCP&lt;/strong&gt; → Extract intent from user queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geocoding MCP&lt;/strong&gt; → Translate locations to coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenWeather MCP&lt;/strong&gt; → Get forecasts for planning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airbnb MCP&lt;/strong&gt; → Search for lodging options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM MCP&lt;/strong&gt; → Generate a natural language itinerary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such modularity makes it easier for companies to maintain, update, and scale individual components without breaking the entire system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages and Best Practices
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;Consistent APIs reduce integration overhead and enable faster onboarding for new teams.&lt;/li&gt;
&lt;li&gt;Shareable images facilitate collaboration and standardization across departments.&lt;/li&gt;
&lt;li&gt;Image signing and vulnerability scanning with Docker Scout help maintain security compliance.&lt;/li&gt;
&lt;li&gt;Portable images work on-premises, in the cloud, or in hybrid environments.&lt;/li&gt;
&lt;li&gt;Supports policy enforcement and auditability for highly regulated industries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Docker Hardened Images to ensure base image security.&lt;/li&gt;
&lt;li&gt;Enforce consistent versioning and tagging to maintain traceability.&lt;/li&gt;
&lt;li&gt;Automate vulnerability scanning in CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;Maintain private registries for proprietary MCP images to secure intellectual property.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Docker MCP transforms the deployment of AI and ML services from a fragile, bespoke process into a secure, standardized, and highly portable approach. By defining clear APIs, offering discoverable metadata, and enabling composable workflows, MCP empowers organizations to build maintainable, production-grade, agentic systems.&lt;/p&gt;

&lt;p&gt;With Docker MCP, teams can focus on delivering real business value rather than reinventing deployment and integration for every new model or service.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ai</category>
      <category>devops</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Modern Docker Made Easy: Real Apps, Volumes, and Live Resource Updates</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Sun, 22 Jun 2025 02:34:04 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/modern-docker-made-easy-real-apps-volumes-and-live-resource-updates-5a76</link>
      <guid>https://forem.com/raju_dandigam/modern-docker-made-easy-real-apps-volumes-and-live-resource-updates-5a76</guid>
      <description>&lt;p&gt;Docker remains a cornerstone of modern dev and DevOps workflows. With Docker Desktop 4.42, released in June 2025, Docker now offers &lt;strong&gt;native IPv6, built-in MCP Toolkit&lt;/strong&gt;, and expanded &lt;strong&gt;Model Runner AI capabilities&lt;/strong&gt;—features that significantly enhance real-world development environments&lt;/p&gt;

&lt;p&gt;In this guide, we will build a containerized app featuring persistent data, runtime tuning, monitoring, security, and more—with commands and concepts oriented toward professional use.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Practical Value of These Docker Concepts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network parity&lt;/strong&gt;: IPv6 support eliminates environment discrepancies and future-proofs setups .&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI workflows&lt;/strong&gt;: With docker model… commands, you can pull and test LLMs locally from the CLI .&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service catalog&lt;/strong&gt;: The integrated MCP Toolkit gives instant access to 100+ tools—like GitHub, MongoDB—isolated in secure containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro-grade features&lt;/strong&gt;: This demo includes dynamic resource updates, monitoring, lifecycle control, and cleanups.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The Mini-App: Ping Counter
&lt;/h3&gt;

&lt;p&gt;We’ll run a Node.js script that reads and increments a counter in &lt;code&gt;counter.json&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;project/
├─ Dockerfile
├─ counter.json  ← initializes as {"value":0}
└─ index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fs = require('fs');
const data = JSON.parse(fs.readFileSync('./counter.json'));
data.value += 1;
fs.writeFileSync('./counter.json', JSON.stringify(data));
console.log(`Count is now ${data.value}`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple script demonstrates containers, images, volumes, monitoring, and dynamic tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Build the image
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:20-alpine
WORKDIR /app

COPY package.json .
RUN npm install --production
COPY . .

CMD ["node", "index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and inspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t ping-counter:1.0 .
docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This highlights Docker’s layered approach and the modern docker image command suite.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Initial Run (Stateless)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo '{"value":0}' &amp;gt; counter.json
docker run --name no-store ping-counter:1.0
# → Count is now 1
docker run --name no-store2 ping-counter:1.0
# → Count resets to 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows that containers default to a fresh state every run.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add Persistence via Volumes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d \
  --name counter \
  -v counter_data:/app/counter.json \
  ping-counter:1.0
docker logs counter           # → Count is now 1
docker restart counter &amp;amp;&amp;amp; docker logs counter  # → Count increments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Volumes allow state persistence beyond container lifecycles—key for real-world apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Dynamic Runtime Tuning
&lt;/h3&gt;

&lt;p&gt;Modify CPU and memory limits on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker update --cpus="0.5" --memory="128m" counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This live update doesn't require a container restart&lt;/li&gt;
&lt;li&gt;Flags like &lt;code&gt;--cpus&lt;/code&gt;, &lt;code&gt;--memory&lt;/code&gt;, and &lt;code&gt;--cpu-shares&lt;/code&gt; manage cgroup constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Live Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker stats counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitors runtime resource consumption. For deeper analytics, integrate Docker Desktop’s Resource Usage or Prometheus/OpenTelemetry .&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Rebuild &amp;amp; Redeploy
&lt;/h3&gt;

&lt;p&gt;If you change index.js to increment by five instead, do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t ping-counter:1.1 .
docker stop counter &amp;amp;&amp;amp; docker rm counter
docker run -d \
  --name counter \
  -v counter_data:/app/counter.json \
  ping-counter:1.1
docker logs counter  # → reflects new count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9. Clean-Up &amp;amp; Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker ps -a
docker images
docker volume ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean unused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker rm no-store no-store2
docker rmi ping-counter:1.0
docker volume prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use curated/hardened base images.&lt;/li&gt;
&lt;li&gt;Scan using Docker Scout or Trivy.&lt;/li&gt;
&lt;li&gt;Implement SBOM and image signing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Advanced Concepts Snapshot
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Health checks (HEALTHCHECK) to detect container readiness.&lt;/li&gt;
&lt;li&gt;Non-root execution enhances runtime security.&lt;/li&gt;
&lt;li&gt;IPv6 toggling within Docker Desktop network settings&lt;/li&gt;
&lt;li&gt;AI model experimentation: use docker model pull and docker model run.&lt;/li&gt;
&lt;li&gt;MCP Toolkit: run MCP tools via docker mcp or UI&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In this guide, we built a real-world Dockerized app from scratch—covering images, containers, persistent volumes, live resource tuning, monitoring, and security best practices. We also touched on modern features like IPv6 support and AI model tooling.&lt;/p&gt;

&lt;p&gt;With this foundation, we’re now equipped to apply Docker confidently in professional workflows—and ready to take the next step into orchestration, CI/CD, or advanced container security.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Smarter JavaScript in 2025: 10 TypeScript Features You Can’t Ignore</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Wed, 28 May 2025 06:14:35 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/smarter-javascript-in-2025-10-typescript-features-you-cant-ignore-5cf1</link>
      <guid>https://forem.com/raju_dandigam/smarter-javascript-in-2025-10-typescript-features-you-cant-ignore-5cf1</guid>
      <description>&lt;p&gt;JavaScript’s flexibility remains both its strength and Achilles’ heel. In 2025, TypeScript has firmly established itself as the intelligent layer that brings structure, clarity, and safety to modern JavaScript development. This article highlights 10 advanced TypeScript features that can transform how you write, reason about, and maintain your codebase, whether you're building robust frontends or scalable full-stack applications.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Type Inference That Feels Like Magic
&lt;/h4&gt;

&lt;p&gt;TypeScript has become incredibly good at figuring out types automatically. You don't need to define everything explicitly—TS understands your intentions through context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const user = {
  name: "Raju",
  age: 25,
};
// TypeScript infers: { name: string; age: number }

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

&lt;/div&gt;



&lt;p&gt;Why it matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces boilerplate&lt;/li&gt;
&lt;li&gt;Keeps your code clean while staying safe&lt;/li&gt;
&lt;li&gt;Accelerates development without sacrificing type safety&lt;/li&gt;
&lt;li&gt;Leverages contextual typing and improved tooling support&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. The satisfies Operator
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;satisfies&lt;/code&gt; keyword ensures that an object conforms to a particular type, while still keeping its narrower literal types intact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const point = { x: 1, y: 2 } satisfies { x: number; y: number };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use case: When you want stricter compile-time checks but want to retain type precision for operations.&lt;br&gt;
Bonus: Unlike &lt;code&gt;as&lt;/code&gt;, which coerces types, &lt;code&gt;satisfies&lt;/code&gt; provides better safety and does not override literal types.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Module Augmentation for Third-Party Extensions
&lt;/h4&gt;

&lt;p&gt;With module augmentation, you can extend existing libraries like Express without touching their core types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;declare module 'express' {
  interface Request {
    user?: { id: string; role: string };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it's useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhances functionality while keeping types safe&lt;/li&gt;
&lt;li&gt;Improves autocompletion and code readability&lt;/li&gt;
&lt;li&gt;Ideal for plugin-based or loosely typed packages&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Template Literal Types
&lt;/h4&gt;

&lt;p&gt;Template literal types allow you to generate string combinations dynamically at the type level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Direction = `scroll-${'top' | 'bottom' | 'left' | 'right'}`;
type ButtonVariant = `btn-${'primary' | 'secondary' | 'tertiary'}`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When to use: Design systems, route names, dynamic event types, or CSS utility libraries.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Discriminated Unions
&lt;/h4&gt;

&lt;p&gt;Discriminated unions are powerful for working with complex state logic and conditional flows. They simplify switch/case scenarios and reduce runtime bugs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Result = 
  | { type: "success"; data: string }
  | { type: "error"; message: string };

function handle(result: Result) {
  if (result.type === "success") {
    console.log(result.data);
  } else {
    console.error(result.message);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real-world benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner conditional logic&lt;/li&gt;
&lt;li&gt;Safer handling of multiple object shapes&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;never&lt;/code&gt; to enforce exhaustive checks in switch-case logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. Seamless Integration with Zod for Runtime Type Safety
&lt;/h4&gt;

&lt;p&gt;Tools like Zod complement TypeScript by validating data at runtime, which TS alone doesn't cover.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { z } from 'zod';
const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});
type User = z.infer&amp;lt;typeof userSchema&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it’s effective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aligns runtime and compile-time validation&lt;/li&gt;
&lt;li&gt;Great for form validation, APIs, and data sanitization&lt;/li&gt;
&lt;li&gt;Alternative: io-ts, yup&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  7. Variadic Tuple Types
&lt;/h4&gt;

&lt;p&gt;Need to type a function that accepts and returns dynamic arguments? Variadic tuple types are designed for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function logArgs&amp;lt;T extends unknown[]&amp;gt;(...args: T): T {
  console.log(...args);
  return args;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where it's used: Logging functions, higher-order utilities, middleware wrappers—anywhere flexibility meets type safety.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Custom Utility Types
&lt;/h4&gt;

&lt;p&gt;Beyond built-in utilities like Partial, Omit, and Pick, TypeScript allows you to create custom utilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Writable&amp;lt;T&amp;gt; = {
  -readonly [P in keyof T]: T[P];
};

// Chaining example
type MutableAndOptional&amp;lt;T&amp;gt; = {
  -readonly [P in keyof T]?: T[P];
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Practical usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toggle immutability&lt;/li&gt;
&lt;li&gt;Transform libraries' read-only types into editable ones&lt;/li&gt;
&lt;li&gt;Create flexible, generic helpers for your codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  9. Type Guards and Assertion Functions
&lt;/h4&gt;

&lt;p&gt;TypeScript’s support for type assertions using custom guard functions makes runtime validation safer and cleaner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isUser(value: any): value is User {
  return value &amp;amp;&amp;amp; typeof value.name === 'string';
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes runtime checks type-aware&lt;/li&gt;
&lt;li&gt;Helps the compiler narrow down types automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  10. Type-Safe Routing in Frameworks like Next.js
&lt;/h4&gt;

&lt;p&gt;Modern frameworks like Next.js deeply integrate TypeScript into their routing, navigation, and data fetching systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const path = `/user/${user.id}` as const;
router.push(path);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents broken links and route mismatches&lt;/li&gt;
&lt;li&gt;Improves autocompletion and debugging experience&lt;/li&gt;
&lt;li&gt;Enforces consistency across the frontend&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In 2025, TypeScript has moved far beyond being a superset of JavaScript—it’s a cornerstone for building maintainable, intelligent, and scalable applications. Whether optimizing a legacy system or architecting a greenfield project, TypeScript's advanced type system, tooling integration, and runtime synergy with libraries like Zod allow you to ship faster without sacrificing reliability.&lt;br&gt;
Mastering these 10 features will enhance your developer experience and set you up for success in any modern JavaScript ecosystem.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>ui</category>
    </item>
    <item>
      <title>Template Revolution: Angular’s New Syntax That You Should Be Using Today</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Sat, 12 Apr 2025 23:47:18 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/template-revolution-angulars-new-syntax-that-you-should-be-using-today-404m</link>
      <guid>https://forem.com/raju_dandigam/template-revolution-angulars-new-syntax-that-you-should-be-using-today-404m</guid>
      <description>&lt;p&gt;Angular 17 introduced a suite of powerful new control flow directives: &lt;code&gt;@if&lt;/code&gt;, &lt;code&gt;@for&lt;/code&gt;, &lt;code&gt;@switch&lt;/code&gt;, and &lt;code&gt;@defer&lt;/code&gt;. These features bring a cleaner, more performant, and readable syntax to Angular templates, replacing the traditional structural directives like &lt;code&gt;*ngIf&lt;/code&gt;, &lt;code&gt;*ngFor&lt;/code&gt;, and &lt;code&gt;ngSwitch&lt;/code&gt;. In this blog, we’ll explore each of these new directives in detail, comparing the traditional and modern approaches with examples, and discussing their benefits, performance implications, and industry adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance and Optimization Benefits
&lt;/h3&gt;

&lt;p&gt;Angular's new control flow syntax is not just syntactic improvement—it introduces significant performance benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zoneless Compatibility&lt;/strong&gt;: These directives are optimized to work without relying on &lt;code&gt;zone.js&lt;/code&gt;, reducing unnecessary change detection cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller Bundle Sizes&lt;/strong&gt;: The new syntax reduces reliance on extra &lt;code&gt;&amp;lt;ng-template&amp;gt;&lt;/code&gt; nodes and directives, contributing to a leaner DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Template Evaluation&lt;/strong&gt;: Directives are compiled down to more efficient instructions, speeding up render time and updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Memory Usage&lt;/strong&gt;: With deferred loading (&lt;code&gt;@defer&lt;/code&gt;), components that aren't immediately needed aren't created, reducing memory consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Data
&lt;/h3&gt;

&lt;p&gt;According to Angular team benchmarks, adoption of these new syntaxes in large-scale apps has shown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up to 45% reduction in change detection cycles for deferred content.&lt;/li&gt;
&lt;li&gt;10–20% improvement in initial rendering times in views utilizing &lt;code&gt;@if&lt;/code&gt; and &lt;code&gt;@for&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reduced template complexity leads to better hydration speed in SSR/Angular Universal applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;@if&lt;/code&gt; – Conditional Rendering
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@if&lt;/code&gt; directive allows developers to conditionally render parts of the template without relying on &lt;code&gt;&amp;lt;ng-template&amp;gt;&lt;/code&gt; syntax. It promotes cleaner and more intuitive templates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (with &lt;code&gt;*ngIf&lt;/code&gt;):&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;&amp;lt;div *ngIf="user &amp;amp;&amp;amp; user.isLoggedIn; else guest"&amp;gt;
  &amp;lt;h2&amp;gt;Welcome, {{ user.name }}!&amp;lt;/h2&amp;gt;
  &amp;lt;div *ngIf="user.notifications.length &amp;gt; 0"&amp;gt;
    &amp;lt;p&amp;gt;You have {{ user.notifications.length }} new notifications.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;ng-template #guest&amp;gt;
  &amp;lt;h2&amp;gt;Hello, Guest!&amp;lt;/h2&amp;gt;
  &amp;lt;button (click)="login()"&amp;gt;Login&amp;lt;/button&amp;gt;
&amp;lt;/ng-template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (with &lt;code&gt;@if&lt;/code&gt;):&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;@if (user?.isLoggedIn) {
  &amp;lt;h2&amp;gt;Welcome, {{ user.name }}!&amp;lt;/h2&amp;gt;
  @if (user.notifications.length &amp;gt; 0) {
    &amp;lt;p&amp;gt;You have {{ user.notifications.length }} new notifications.&amp;lt;/p&amp;gt;
  }
} @else {
  &amp;lt;h2&amp;gt;Hello, Guest!&amp;lt;/h2&amp;gt;
  &amp;lt;button (click)="login()"&amp;gt;Login&amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Cleaner nested conditional rendering&lt;/li&gt;
&lt;li&gt;Avoids template bloating and nesting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;@for&lt;/code&gt; – Enhanced Iteration&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;@for&lt;/code&gt; directive offers a performance-optimized and more readable way to iterate over collections. It also includes contextual variables like &lt;code&gt;$index&lt;/code&gt;, &lt;code&gt;$first&lt;/code&gt;, &lt;code&gt;$last&lt;/code&gt;, and built-in empty state handling.&lt;/p&gt;

&lt;p&gt;Before (with *ngFor):&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;div *ngIf="orders.length &amp;gt; 0; else noOrders"&amp;gt;
  &amp;lt;div *ngFor="let order of orders; let i = index; trackBy: trackByOrderId"&amp;gt;
    &amp;lt;h3&amp;gt;Order #{{ i + 1 }} - {{ order.id }}&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;Status: {{ order.status }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;ng-template #noOrders&amp;gt;
  &amp;lt;p&amp;gt;No recent orders found.&amp;lt;/p&amp;gt;
&amp;lt;/ng-template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (with &lt;a class="mentioned-user" href="https://dev.to/for"&gt;@for&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@for (let order of orders; track order.id) {
  &amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt;Order #{{ $index + 1 }} - {{ order.id }}&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;Status: {{ order.status }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
} @empty {
  &amp;lt;p&amp;gt;No recent orders found.&amp;lt;/p&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Declarative and self-contained rendering logic]&lt;/li&gt;
&lt;li&gt;Built-in support for fallback UI with &lt;code&gt;@empty&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;@switch&lt;/code&gt; – Declarative Multi-way Branching&lt;/strong&gt;&lt;br&gt;
This directive replicates JavaScript’s &lt;code&gt;switch-case&lt;/code&gt; syntax for templates. It allows rendering different blocks based on a variable's value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (with &lt;code&gt;ngSwitch&lt;/code&gt;):&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;&amp;lt;div [ngSwitch]="device.status"&amp;gt;
  &amp;lt;p *ngSwitchCase="'online'"&amp;gt;Device is Online&amp;lt;/p&amp;gt;
  &amp;lt;p *ngSwitchCase="'offline'"&amp;gt;Device is Offline&amp;lt;/p&amp;gt;
  &amp;lt;p *ngSwitchCase="'error'"&amp;gt;Error connecting to device&amp;lt;/p&amp;gt;
  &amp;lt;p *ngSwitchDefault&amp;gt;Unknown status&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (with &lt;code&gt;@switch&lt;/code&gt;):&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;@switch (device.status) {
  @case ('online') {
    &amp;lt;p&amp;gt;Device is Online&amp;lt;/p&amp;gt;
  }
  @case ('offline') {
    &amp;lt;p&amp;gt;Device is Offline&amp;lt;/p&amp;gt;
  }
  @case ('error') {
    &amp;lt;p&amp;gt;Error connecting to device&amp;lt;/p&amp;gt;
  }
  @default {
    &amp;lt;p&amp;gt;Unknown status&amp;lt;/p&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Reduces repetitive directive syntax&lt;/li&gt;
&lt;li&gt;Easier to maintain conditional UI flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;@defer&lt;/code&gt; – Lazy Loading Made Simple&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;@defer&lt;/code&gt; directive enables lazy loading of components or template blocks based on triggers like idle time, interaction, viewport visibility, or custom logic.&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;@defer (on viewport; prefetch on idle) {
  &amp;lt;app-dashboard-analytics /&amp;gt;
} @placeholder {
  &amp;lt;p&amp;gt;Loading analytics module...&amp;lt;/p&amp;gt;
} @loading {
  &amp;lt;spinner /&amp;gt;
} @error {
  &amp;lt;p&amp;gt;Failed to load analytics. Please try again.&amp;lt;/p&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Granular control over when content loads&lt;/li&gt;
&lt;li&gt;Improves time-to-interactive (TTI)&lt;/li&gt;
&lt;li&gt;Minimizes memory footprint for large apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Comparison Table&lt;/strong&gt;&lt;br&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%2Fbeufnzh3pq1pfnuxepz2.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%2Fbeufnzh3pq1pfnuxepz2.png" alt=" " width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Angular’s new control flow syntax modernizes how developers write template logic. If you’re starting a new project, embracing these directives can greatly simplify your templates and improve maintainability. For existing codebases, a gradual migration strategy could yield both readability and performance gains.&lt;/p&gt;

&lt;p&gt;The combination of lazy loading (&lt;code&gt;@defer&lt;/code&gt;), cleaner conditional rendering (&lt;code&gt;@if&lt;/code&gt;), optimized iteration (&lt;code&gt;@for&lt;/code&gt;), and structured branching (&lt;code&gt;@switch&lt;/code&gt;) marks a significant evolution in Angular's templating system—making it easier to build performant, scalable web applications.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>programming</category>
      <category>typescript</category>
    </item>
    <item>
      <title>A Complete Guide to Angular Component Testing with Cypress</title>
      <dc:creator>Raju Dandigam</dc:creator>
      <pubDate>Sun, 06 Apr 2025 07:28:23 +0000</pubDate>
      <link>https://forem.com/raju_dandigam/a-complete-guide-to-angular-component-testing-with-cypress-5g5b</link>
      <guid>https://forem.com/raju_dandigam/a-complete-guide-to-angular-component-testing-with-cypress-5g5b</guid>
      <description>&lt;p&gt;Angular's latest versions have introduced exciting features like signals, computed, and enhanced reactivity. Paired with Cypress Component Testing, developers now have a powerful toolkit to write fast, realistic, and robust tests. This post dives deep into Angular component testing using Cypress, including how to test components that use signals, observables, and mock services with confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Why Cypress for Angular Component Testing?
&lt;/h3&gt;

&lt;p&gt;Cypress isn't just for end-to-end tests. With Cypress Component Testing, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render Angular components in isolation&lt;/li&gt;
&lt;li&gt;Interact with them as a user would&lt;/li&gt;
&lt;li&gt;Spy on inputs/outputs&lt;/li&gt;
&lt;li&gt;Work seamlessly with Angular DI, modules, and modern features like signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔧 Setting Up Cypress Component Testing&lt;br&gt;
Install Cypress and initialize component testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install cypress @cypress/angular --save-dev
npx cypress open --component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the UI wizard to configure your Angular workspace. It will auto-generate a Cypress configuration compatible with Angular.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠️ Example Component with Signals &amp;amp; Outputs
&lt;/h3&gt;

&lt;p&gt;Let's test a simple &lt;code&gt;CounterComponent&lt;/code&gt; that demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input binding&lt;/li&gt;
&lt;li&gt;Output emission&lt;/li&gt;
&lt;li&gt;Signals and computed values
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// counter.component.ts
import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{{ title }}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;Count: {{ count() }}&amp;lt;/p&amp;gt;
      &amp;lt;button (click)="increment()"&amp;gt;Increment&amp;lt;/button&amp;gt;
      &amp;lt;button (click)="reset()"&amp;gt;Reset&amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;Double: {{ doubleCount() }}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  `
})
export class CounterComponent {
  @Input() title = 'Default Counter';
  @Output() onReset = new EventEmitter&amp;lt;void&amp;gt;();

  private _count = signal(0);
  count = this._count.asReadonly();
  doubleCount = computed(() =&amp;gt; this._count() * 2);

  increment() {
    this._count.set(this._count() + 1);
  }

  reset() {
    this._count.set(0);
    this.onReset.emit();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔮 Basic Cypress Tests
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;✅ Rendering with Default and Custom Inputs&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;// counter.component.cy.ts
import { mount } from 'cypress/angular';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () =&amp;gt; {
  it('renders with default title', () =&amp;gt; {
    mount(CounterComponent);
    cy.contains('Default Counter');
    cy.contains('Count: 0');
  });

  it('accepts custom title', () =&amp;gt; {
    mount(CounterComponent, {
      componentProperties: { title: 'Custom Counter' }
    });
    cy.contains('Custom Counter');
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚖️ Testing Signals and Computed Values
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it('increments count and updates double', () =&amp;gt; {
  mount(CounterComponent);
  cy.get('button').contains('Increment').click().click();
  cy.contains('Count: 2');
  cy.contains('Double: 4');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📢 Spying on Output Events
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it('emits reset event', () =&amp;gt; {
  const resetSpy = cy.spy().as('resetSpy');

  mount(CounterComponent, {
    componentProperties: {
      onReset: { emit: resetSpy } as any
    }
  });

  cy.get('button').contains('Reset').click();
  cy.get('@resetSpy').should('have.been.calledOnce');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📃 Testing with Observables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// message.component.ts
@Component({
  selector: 'app-message',
  template: `&amp;lt;p *ngIf="message"&amp;gt;{{ message }}&amp;lt;/p&amp;gt;`
})
export class MessageComponent {
  private _message = signal('');
  message = this._message.asReadonly();

  @Input()
  set messageInput$(value: Observable&amp;lt;string&amp;gt;) {
    value.subscribe(msg =&amp;gt; this._message.set(msg));
  }
}
it('renders observable message', () =&amp;gt; {
  const message$ = of('Hello from Observable!');
  mount(MessageComponent, {
    componentProperties: { messageInput$: message$ }
  });
  cy.contains('Hello from Observable!');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔜 Advanced Mocking Techniques
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;⚙️ Injecting Angular Services&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;class MockLoggerService {
  log = cy.stub().as('logStub');
}

mount(MyComponent, {
  providers: [
    { provide: LoggerService, useClass: MockLoggerService }
  ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌐 Mocking HTTP Calls with HttpClientTestingModule
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

beforeEach(() =&amp;gt; {
  cy.intercept('GET', '/api/data', { fixture: 'data.json' });
});

mount(MyHttpComponent, {
  imports: [HttpClientTestingModule]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚪 Stubbing Child Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component({
  selector: 'app-child',
  template: '' // stubbed template
})
class StubChildComponent {}

mount(ParentComponent, {
  declarations: [StubChildComponent]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 Conclusion
&lt;/h3&gt;

&lt;p&gt;Cypress Component Testing is a game-changer for Angular developers. By combining the real browser testing experience with Angular’s new reactivity features like signals and computed, we can create robust, testable, and modern UIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mount Angular components with Cypress easily&lt;/li&gt;
&lt;li&gt;Test signals, computed, observables&lt;/li&gt;
&lt;li&gt;Use spies and stubs for robust unit isolation&lt;/li&gt;
&lt;li&gt;Mock services and HTTP with Angular-friendly tooling&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web</category>
      <category>cyress</category>
      <category>angular</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
