<?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: Kevin Julián Martínez Escobar</title>
    <description>The latest articles on Forem by Kevin Julián Martínez Escobar (@kevinccbsg).</description>
    <link>https://forem.com/kevinccbsg</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%2F304931%2F44aabcff-3ed1-4c8c-91e3-5156b73b83a1.jpeg</url>
      <title>Forem: Kevin Julián Martínez Escobar</title>
      <link>https://forem.com/kevinccbsg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kevinccbsg"/>
    <language>en</language>
    <item>
      <title>Testing Payment Flows Without the Payment SDK</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 19:04:53 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/testing-payment-flows-without-the-payment-sdk-3obm</link>
      <guid>https://forem.com/kevinccbsg/testing-payment-flows-without-the-payment-sdk-3obm</guid>
      <description>&lt;p&gt;Payment integrations are one of the hardest things to test in a web app. The SDK renders its own UI, controls its own form fields, and fires callbacks when the user completes a payment. You can't programmatically fill in a credit card number. You can't simulate a declined card. And if the SDK fails to initialize — because of a network issue, a bad API key, or a test environment misconfiguration — your entire test falls apart.&lt;/p&gt;

&lt;p&gt;You can mock the SDK's setup endpoint to get the SDK rendering, the form mounting, the session resolving. That covers surface area — but it stops there. It doesn't test what happens &lt;em&gt;after&lt;/em&gt; the payment resolves: the API calls, the analytics events, the navigation, the error states. The part that actually matters.&lt;/p&gt;

&lt;p&gt;This article shows a different approach: using TWD's component mocking to replace the payment SDK entirely with a simple mock that gives you full control over the payment lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test What You Own. Mock What You Don't.
&lt;/h2&gt;

&lt;p&gt;That's TWD's philosophy, and it's the whole reason component mocking is the right tool here. The payment SDK is someone else's code — its internals and lifecycle are their problem, covered by their test suite. Your responsibility is the seam: the callbacks fired into your app, the API calls they trigger, the analytics events, the UI state. That's where your bugs ship from.&lt;/p&gt;

&lt;p&gt;You won't exercise the real SDK in these tests. That's the tradeoff — and it's deliberate. What you gain is the ability to exercise &lt;em&gt;your&lt;/em&gt; side of the integration exhaustively: every callback, every branch, every error path. The SDK's correctness is the vendor's concern. The correctness of everything your app does around it is yours, and that's what these tests finally reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A typical payment component looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PaymentDropIn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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;checkout&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;PaymentSDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;onPaymentCompleted&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;confirmOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;trackPurchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/success&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;onPaymentFailed&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;trackPaymentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&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="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is tangled inside one component: the SDK initialization, the business logic, the analytics, the navigation, the error handling. You can't test the &lt;code&gt;onPaymentCompleted&lt;/code&gt; callback without actually initializing the SDK. And you can't initialize the SDK without a real (or carefully mocked) payment session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Separate the SDK from the Logic
&lt;/h2&gt;

&lt;p&gt;The fix is architectural. Move the callback logic &lt;em&gt;out&lt;/em&gt; of the payment component and into the parent. The payment component becomes a thin SDK wrapper that receives callbacks as props:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Thin wrapper — just the SDK&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PaymentDropIn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onCompleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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;checkout&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;PaymentSDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;onPaymentCompleted&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="nf"&gt;onCompleted&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;onPaymentFailed&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onFailed&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="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onError&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="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Parent — owns the business logic&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CheckoutPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&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;handleCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;confirmOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;trackPurchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/success&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;handleFailed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&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;trackPaymentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentDropIn&lt;/span&gt;
      &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onCompleted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleCompleted&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onFailed&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleFailed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleError&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&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;This is a good refactor regardless of testing. The parent owns the business logic. The payment component owns the SDK. Clean separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Wrap for Mocking
&lt;/h2&gt;

&lt;p&gt;TWD provides &lt;code&gt;MockedComponent&lt;/code&gt; — a wrapper that lets tests replace a component's children with a mock. Wrap the payment component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MockedComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twd-js/ui&lt;/span&gt;&lt;span class="dl"&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;PaymentDropIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MockedComponent&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"paymentDropIn"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentDropInContent&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MockedComponent&lt;/span&gt;&lt;span class="p"&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;In production, &lt;code&gt;MockedComponent&lt;/code&gt; is a transparent pass-through — it renders its children. In tests, &lt;code&gt;twd.mockComponent("paymentDropIn", ...)&lt;/code&gt; replaces the children with whatever you provide.&lt;/p&gt;

&lt;p&gt;One important detail: &lt;code&gt;MockedComponent&lt;/code&gt; passes its child's &lt;em&gt;props&lt;/em&gt; to the mock component. That's why we need &lt;code&gt;PaymentDropInContent&lt;/code&gt; as a separate component that receives all the callback props — so the mock receives them too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build the Mock
&lt;/h2&gt;

&lt;p&gt;The mock is dead simple. Three buttons — one per payment outcome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paymentDropIn&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;onCompleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;onCompleted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Pay&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;onFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Refused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Fail Payment&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SDK crashed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;Click "Pay" and the parent's &lt;code&gt;handleCompleted&lt;/code&gt; fires — calling &lt;code&gt;confirmOrder&lt;/code&gt;, sending the purchase event, navigating to success. Click "Fail Payment" and &lt;code&gt;handleFailed&lt;/code&gt; fires — sending the error event, showing the error banner. No SDK involved. Just callbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Test Everything
&lt;/h2&gt;

&lt;p&gt;Now you can test the full payment lifecycle with standard TWD patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should call confirmOrder and navigate to success&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmOrder&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/api/orders/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/confirm`&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="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cust-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;order_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... fill form, submit, wait for payment session ...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payButton&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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;payButton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify the API was called&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmOrder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify navigation&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&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="s2"&gt;contain.url&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;/success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should fire purchase_error when payment is declined&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... setup ...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failButton&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fail Payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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;failButton&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;errorEvent&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitFor&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;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purchase_error&lt;/span&gt;&lt;span class="dl"&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;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Event not found&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;ev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Refused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should show error banner when confirmOrder fails&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmOrderFail&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/api/orders/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/confirm`&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="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... setup ...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payButton&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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;payButton&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;errorBanner&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-testid='payment-error']&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;errorBanner&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="s2"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&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="s2"&gt;not.contain.url&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;/success&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;h2&gt;
  
  
  What This Pattern Gives You
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Coverage you couldn't get before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analytics events fire with the correct data (payment type, transaction ID, error codes)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;confirmOrder&lt;/code&gt; API is called with the right order ID&lt;/li&gt;
&lt;li&gt;Navigation to the success page happens after payment, not before&lt;/li&gt;
&lt;li&gt;Error banners appear when the API fails&lt;/li&gt;
&lt;li&gt;Error banners appear when the payment is declined&lt;/li&gt;
&lt;li&gt;Error banners appear when the SDK crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; These tests run in ~1 second each. No SDK initialization, no payment session setup, no Adyen/Stripe endpoint mocking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability:&lt;/strong&gt; No more flaky tests that break because the payment SDK's test environment is down. The mock is deterministic.&lt;/p&gt;

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

&lt;p&gt;The unlock is component mocking. TWD's &lt;code&gt;MockedComponent&lt;/code&gt; lets you replace a third-party SDK in tests with a simple stand-in whose callbacks you fire on demand — so the payment flow, which previously depended on an un-drivable SDK, becomes three buttons and a set of assertions. The SDK never boots. Tests run in a second. The callback flow — API calls, analytics, navigation, error states — is finally exercised.&lt;/p&gt;

&lt;p&gt;The thin-wrapper refactor is what makes that possible, but it's the enabler, not the point. Once it's in place, the pattern transfers to any third-party component that fires callbacks: map SDKs, video players, chat widgets, auth flows. Same shape every time — wrap the component, swap it in tests.&lt;/p&gt;

&lt;p&gt;Existing tests that mock the SDK's setup endpoint still work; they cover different ground. The component mock picks up where those stop.&lt;/p&gt;

&lt;p&gt;More on the feature at &lt;a href="https://twd.dev/component-mocking.html" rel="noopener noreferrer"&gt;twd.dev/component-mocking&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>payments</category>
      <category>mocking</category>
    </item>
    <item>
      <title>When Your Mocks Lie: Contract Testing with TWD</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 19:03:50 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/when-your-mocks-lie-contract-testing-with-twd-2e58</link>
      <guid>https://forem.com/kevinccbsg/when-your-mocks-lie-contract-testing-with-twd-2e58</guid>
      <description>&lt;p&gt;Every mock you write is a claim about what your backend returns. The moment the backend changes — a renamed field, a tightened enum, a new required property — that claim becomes a lie. Your tests still pass. Production breaks.&lt;/p&gt;

&lt;p&gt;This is mock drift, and it's invisible. You don't find out until a user hits a 500 or an empty UI in prod. The mocks that gave you confidence were the thing misleading you.&lt;/p&gt;

&lt;p&gt;TWD's contract testing closes this gap. Every mock response registered in a test gets validated against your OpenAPI spec during the same run that executes the test. A schema mismatch becomes a loud, specific error — in the same output as the test failures. No separate pipeline, no broker, no provider verifier. One command does both.&lt;/p&gt;

&lt;p&gt;This article walks through what contract testing in TWD actually does, how to wire it into an existing project, and what the output looks like when it catches real drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem contract testing solves
&lt;/h2&gt;

&lt;p&gt;Consider a typical mock in a TWD test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userList&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="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;next&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="na"&gt;previous&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="na"&gt;results&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a1b2-...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Acme Corp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10000.00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shape made the test pass three months ago. Since then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The backend team removed &lt;code&gt;balance&lt;/code&gt; from the list endpoint (it's a wallet concept now, served elsewhere).&lt;/li&gt;
&lt;li&gt;A new required field &lt;code&gt;external_id&lt;/code&gt; was added.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;discount&lt;/code&gt; field format tightened from &lt;code&gt;"15"&lt;/code&gt; to &lt;code&gt;"15.00"&lt;/code&gt; (two decimals).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these changes break the test. The component receives exactly the shape the mock provides. The test is green. Everything looks fine.&lt;/p&gt;

&lt;p&gt;Meanwhile in production, the real API returns &lt;code&gt;external_id&lt;/code&gt; (which a column in the table now expects), omits &lt;code&gt;balance&lt;/code&gt; (which a detail drawer is still reading), and sends &lt;code&gt;"10.00"&lt;/code&gt; where the formatter assumes trailing decimals. Bugs ship.&lt;/p&gt;

&lt;p&gt;The test was never wrong — it was testing the wrong reality. The mock had drifted from the contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What TWD does about it
&lt;/h2&gt;

&lt;p&gt;TWD's contract testing runs as part of &lt;code&gt;npx twd-cli run&lt;/code&gt; — the headless runner you'd typically invoke in CI, not the live sidebar you use during local dev. Your inner loop stays fast; drift gets surfaced on every push.&lt;/p&gt;

&lt;p&gt;On every call to &lt;code&gt;twd.mockRequest()&lt;/code&gt;, the response payload is collected. After tests run, each response is validated against the OpenAPI schema for the endpoint that the mock targets.&lt;/p&gt;

&lt;p&gt;The validation uses &lt;a href="https://www.npmjs.com/package/openapi-mock-validator" rel="noopener noreferrer"&gt;&lt;code&gt;openapi-mock-validator&lt;/code&gt;&lt;/a&gt; under the hood and covers what you'd expect from JSON Schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Types (&lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;integer&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;array&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;String formats (&lt;code&gt;uuid&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;date-time&lt;/code&gt;, &lt;code&gt;uri&lt;/code&gt;, and so on)&lt;/li&gt;
&lt;li&gt;Numeric bounds, array constraints, enum values&lt;/li&gt;
&lt;li&gt;Required fields, &lt;code&gt;additionalProperties&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Composition (&lt;code&gt;oneOf&lt;/code&gt;, &lt;code&gt;anyOf&lt;/code&gt;, &lt;code&gt;allOf&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice this means: if your mock returns &lt;code&gt;"id": "user-123"&lt;/code&gt; where the spec says &lt;code&gt;"format": "uuid"&lt;/code&gt;, you hear about it. If your mock omits &lt;code&gt;external_id&lt;/code&gt; where the spec marks it required, you hear about it. If your mock sets &lt;code&gt;"status": "pending"&lt;/code&gt; where the spec enum only allows &lt;code&gt;["COMPLETED", "FAILED", "PENDING"]&lt;/code&gt;, you hear about it.&lt;/p&gt;

&lt;p&gt;The key design choice: &lt;strong&gt;no extra test-writing effort&lt;/strong&gt;. You don't author contract tests separately. The mocks you already write double as contract probes. Two signals from one artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up
&lt;/h2&gt;

&lt;p&gt;Three pieces: get the spec, tell TWD about it, decide how loud to be.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Get the OpenAPI spec
&lt;/h3&gt;

&lt;p&gt;Point TWD at an &lt;code&gt;openapi.json&lt;/code&gt; somewhere on disk. How it gets there is up to you — a &lt;code&gt;curl&lt;/code&gt; against your backend's spec endpoint in CI is the common path. Download fresh on every run so you're always validating against the current contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Configure TWD
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;twd.config.json&lt;/code&gt; at the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:5173"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contractReportPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".twd/contract-report.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retryCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contracts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./openapi.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;source&lt;/code&gt; — path to the OpenAPI JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseUrl&lt;/code&gt; — prefix to strip when matching mock URLs against spec paths. If your mocks call &lt;code&gt;/v1/users&lt;/code&gt; and the spec paths are also &lt;code&gt;/v1/...&lt;/code&gt;, set &lt;code&gt;"/"&lt;/code&gt;. If the spec is served under &lt;code&gt;/api&lt;/code&gt; and your mocks include that prefix, set &lt;code&gt;"/api"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mode&lt;/code&gt; — &lt;code&gt;"warn"&lt;/code&gt; or &lt;code&gt;"error"&lt;/code&gt;. Start with &lt;code&gt;"warn"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict&lt;/code&gt; — whether to reject undocumented response properties.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Decide the mode
&lt;/h3&gt;

&lt;p&gt;This is the one real decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"warn"&lt;/code&gt;&lt;/strong&gt; — mismatches appear in the output but the test run still passes. Good posture when you're introducing contract testing into an existing codebase with accumulated drift. You see what's broken without immediately red-gating the team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"error"&lt;/code&gt;&lt;/strong&gt; — mismatches fail the run. This is where you want to land. It's the only mode that prevents regressions.&lt;/p&gt;

&lt;p&gt;A realistic migration path: start in &lt;code&gt;warn&lt;/code&gt; to surface the backlog, fix mismatches module by module, then flip to &lt;code&gt;error&lt;/code&gt; once you're clean. The flip is the important step — without it, nothing stops new drift from accumulating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TWD ecosystem
&lt;/h2&gt;

&lt;p&gt;Contract testing isn't a standalone library — it's the seam where the TWD packages meet: mocks authored with &lt;a href="https://www.npmjs.com/package/twd-js" rel="noopener noreferrer"&gt;&lt;code&gt;twd-js&lt;/code&gt;&lt;/a&gt;, runs executed by &lt;a href="https://www.npmjs.com/package/twd-cli" rel="noopener noreferrer"&gt;&lt;code&gt;twd-cli&lt;/code&gt;&lt;/a&gt;, validation handled by &lt;a href="https://www.npmjs.com/package/openapi-mock-validator" rel="noopener noreferrer"&gt;&lt;code&gt;openapi-mock-validator&lt;/code&gt;&lt;/a&gt;, and (if you're also using the AI agent skills) the browser bridge through &lt;a href="https://www.npmjs.com/package/twd-relay" rel="noopener noreferrer"&gt;&lt;code&gt;twd-relay&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;If you're starting from zero with TWD, the &lt;a href="https://dev.to/kevinccbsg/guide-to-ai-powered-frontend-testing-with-twd-3i88"&gt;AI-powered frontend testing series&lt;/a&gt; walks through project setup, writing tests, and wiring them into CI. Contract testing slots in once that's working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff: what the output looks like
&lt;/h2&gt;

&lt;p&gt;This is the part worth showing up for — and it exists only because you're already in the TWD stack. Your mocks run through &lt;code&gt;twd-js&lt;/code&gt;. &lt;code&gt;twd-cli&lt;/code&gt; already executes them. The validator just reads what's already moving through your tests. No separate contract test suite, no broker to run, no provider verifier to keep in sync.&lt;/p&gt;

&lt;p&gt;Run your normal test command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx twd-cli run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alongside the usual pass/fail output for each test, you'll see a per-mock contract status line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ GET /v1/users (200) — mock "userList" — in "User list &amp;gt; should display the table"
✗ GET /v1/users/{user_id} (200) — mock "getUser" — in "User detail"
  → response.external_id: missing required property "external_id"
✗ GET /v1/orders (200) — mock "getOrders" — in "User detail"
  → response.next: missing required property "next"
  → response.previous: missing required property "previous"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mocks validated: 253 | Errors: 93 | Warnings: 1 | Skipped: 0

Contract report written to .twd/contract-report.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second failure line — a required property missing on a test that otherwise passes — is where contract testing earns its keep. Without it, the mock keeps serving a shape the real API no longer returns, and the only person who finds out is a user.&lt;/p&gt;

&lt;p&gt;The markdown report is useful for PRs and CI artifacts — it groups failures by endpoint and includes the test name that produced each mock, so tracing a failure back to a specific file is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it looks
&lt;/h2&gt;

&lt;p&gt;Most contract testing tools (Pact being the canonical one) are heavy: brokers, provider verifiers, consumer-driven workflows, separate CI pipelines, coordination between frontend and backend teams. The ceremony is often what kills adoption — teams try it, find it exhausting, and revert to hoping for the best.&lt;/p&gt;

&lt;p&gt;TWD's approach gets maybe 80% of the value for 10% of the cost, because it's opportunistic rather than exhaustive. You're not testing every possible response the backend could emit — you're testing the specific responses your app actually depends on (your mocks). That's often the right target: the place where client assumptions are encoded is exactly the place worth validating.&lt;/p&gt;

&lt;p&gt;And it's cheap to adopt. No broker, no CI changes beyond one step to download the spec, no coordination with the backend team. A consuming team can turn this on unilaterally in an afternoon and immediately benefit.&lt;/p&gt;

&lt;p&gt;The moment the backend ships a breaking change, your next CI run reports it. Not the next deploy. Not the next bug report from a user. The next CI run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into CI
&lt;/h2&gt;

&lt;p&gt;One change to your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download OpenAPI contract&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run contract:download&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;Install service worker&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;npx twd-js init public --save&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 TWD tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx twd-cli run&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;Contract testing report&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;cat .twd/contract-report.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Contract testing isn't the whole pitch — it's one piece of a stack designed to make each part of the testing workflow cheap instead of painful. Adopt TWD and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests that run in your real browser, with a live sidebar as you develop.&lt;/li&gt;
&lt;li&gt;A CI pipeline that's a few lines of YAML away.&lt;/li&gt;
&lt;li&gt;Coverage collected without a separate configuration fight.&lt;/li&gt;
&lt;li&gt;Mocks that double as contract probes, validated against your OpenAPI spec on every run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The opportunity isn't just catching drift. It's that once you're in the TWD stack, everything above comes with it — and each piece is an afternoon of setup, not a quarter of migration.&lt;/p&gt;

&lt;p&gt;More details and the full config reference live at &lt;a href="https://twd.dev/contract-testing.html" rel="noopener noreferrer"&gt;twd.dev/contract-testing&lt;/a&gt;. The project is on GitHub at &lt;a href="https://github.com/BRIKEV/twd" rel="noopener noreferrer"&gt;BRIKEV/twd&lt;/a&gt;. If you find a bug in the validator or want a new format supported, PRs welcome.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>contracts</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Guide to AI-Powered Frontend Testing with TWD</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:54:42 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/guide-to-ai-powered-frontend-testing-with-twd-3i88</link>
      <guid>https://forem.com/kevinccbsg/guide-to-ai-powered-frontend-testing-with-twd-3i88</guid>
      <description>&lt;p&gt;If you've ever watched an AI assistant generate a test file and thought "that looks right" only to spend the next twenty minutes fixing imports, selectors, and mock shapes — this series is for you.&lt;/p&gt;

&lt;p&gt;TWD (Test While Developing) is an in-browser testing library built around a simple idea: tests should run inside your real application, against the real DOM, while you develop. No jsdom. No simulated environments. Just your app, a sidebar showing results in real time, and instant feedback as you code.&lt;/p&gt;

&lt;p&gt;Over the past year, TWD has grown into a full ecosystem — and the part that changed everything is the AI workflow. A set of skills for Claude Code that let an AI agent write tests, execute them in your browser, fix failures, set up CI, find gaps in your coverage, grade the quality of your tests, and generate visual documentation for your whole team.&lt;/p&gt;

&lt;p&gt;This series walks through each piece, in the order you'd use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Series Covers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;a href="https://dev.to/kevinccbsg/stop-letting-ai-write-untestable-code-add-determinism-back-with-twd-3a02"&gt;Stop Letting AI Write Untestable Code. Add Determinism Back with TWD&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The starting point. Run &lt;code&gt;/twd:setup&lt;/code&gt; to analyze your project, answer a few questions, and generate &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; — a configuration file that teaches the AI agent your project's testing conventions. Framework detection, API patterns, auth middleware, third-party modules — all captured in one file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;a href="https://dev.to/kevinccbsg/your-ai-doesnt-just-write-tests-it-runs-them-too-1a5b"&gt;Your AI Doesn't Just Write Tests. It Runs Them Too&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core of the workflow. The &lt;code&gt;/twd&lt;/code&gt; skill writes tests based on your project patterns, sends them to your browser via WebSocket, reads pass/fail results, and iterates until they're green. No screenshots, no heavy payloads — just a tight write-run-fix loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;a href="http://dev.to/kevinccbsg/from-local-tests-to-ci-in-one-command-p9e"&gt;From Local Tests to CI in One Command&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you have tests, &lt;code&gt;/twd:ci-setup&lt;/code&gt; detects your project configuration and generates a GitHub Actions workflow using the official &lt;code&gt;twd-cli&lt;/code&gt; action. Coverage, contract validation, Puppeteer setup — handled automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;a href="https://dev.to/kevinccbsg/your-tests-are-running-but-are-they-covering-the-right-things-55hc"&gt;Your Tests Are Running — But Are They Covering the Right Things?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/twd:test-gaps&lt;/code&gt; skill scans your routes, cross-references them against your test files, and classifies each one as tested, partially tested, or untested. High-risk routes with mutations or permissions are flagged first so you know where to focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;a href="https://dev.to/kevinccbsg/your-tests-pass-but-are-they-good-grading-test-quality-with-twdtest-quality-2077"&gt;Your Tests Pass. But Are They Good?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having tests is not the same as having good tests. The &lt;code&gt;/twd:test-quality&lt;/code&gt; skill grades each test file across four dimensions: journey coverage, interaction depth, assertion quality, and edge case handling. Each file gets a letter grade and actionable suggestions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;a href="https://dev.to/kevinccbsg/turning-your-test-suite-into-a-visual-map-your-whole-team-can-read-2835"&gt;Turning Your Test Suite Into a Visual Map Your Whole Team Can Read&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final piece. The &lt;code&gt;/twd:test-flow-gallery&lt;/code&gt; skill generates Mermaid flowcharts and plain-language summaries from your test files. New developers can understand coverage without reading code. Product can see which user journeys are validated. QA can spot gaps at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Philosophy Behind It
&lt;/h2&gt;

&lt;p&gt;TWD is built on a simple principle: automate what you already verify manually. If you check that a form submits correctly by filling it out in the browser, that's your test. TWD just makes it repeatable.&lt;/p&gt;

&lt;p&gt;The AI workflow extends that same idea. Instead of writing tests after the fact, the agent writes them as part of the development process — using your conventions, running them against your real app, and iterating until they pass.&lt;/p&gt;

&lt;p&gt;The philosophy hasn't changed. What changed is that now your AI agent tests while developing too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install the TWD AI plugin for Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add BRIKEV/twd-ai
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;BRIKEV/twd-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/twd:setup&lt;/code&gt; in your project to kick things off. The rest of the series follows from there.&lt;/p&gt;

&lt;p&gt;Full documentation: &lt;a href="https://twd.dev" rel="noopener noreferrer"&gt;twd.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;github.com/BRIKEV/twd-ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Turning Your Test Suite Into a Visual Map Your Whole Team Can Read</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:49:42 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/turning-your-test-suite-into-a-visual-map-your-whole-team-can-read-2835</link>
      <guid>https://forem.com/kevinccbsg/turning-your-test-suite-into-a-visual-map-your-whole-team-can-read-2835</guid>
      <description>&lt;p&gt;You have written the tests. The CI pipeline runs them. The gap analysis has helped you fill the blind spots. Quality checks are passing. The work is solid.&lt;/p&gt;

&lt;p&gt;And yet — ask a product manager what your test suite actually covers, and you will get a blank stare. Ask a new developer which user flows are tested, and they will spend an hour reading test files to piece it together. Ask QA to verify the coverage makes sense, and they will ask for a document that does not exist.&lt;/p&gt;

&lt;p&gt;This is the last problem the TWD AI workflow solves. And it is not a small one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gap Between Tests and Understanding
&lt;/h2&gt;

&lt;p&gt;Test code is written for machines to execute. It is dense, technical, and full of implementation detail. A test that reads:&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;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getTodos&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;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createTodo&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getTodos&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&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;titleInput&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;titleInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New todo&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;descInput&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;descInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A new task&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;dateInput&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-04-15&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;createButton&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/create todo/i&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&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;createButton&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;rule&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createTodo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New todo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A new task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-04-15&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;...tells a developer exactly what is being tested. It tells a product manager nothing. And for a new team member, figuring out what user journeys are covered means reading through dozens of files like this and piecing it together mentally.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/twd:test-flow-gallery&lt;/code&gt; skill generates that picture for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Skill Produces
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;/twd:test-flow-gallery&lt;/code&gt; in Claude Code (with the &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;TWD AI plugin&lt;/a&gt; installed) analyzes your TWD test files and generates two things for each test file it finds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mermaid flowcharts&lt;/strong&gt; — one per test case. Each chart uses a consistent visual grammar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue rectangles for user actions (clicks, form inputs, navigation)&lt;/li&gt;
&lt;li&gt;Green hexagons for assertions (what the test verifies is true)&lt;/li&gt;
&lt;li&gt;Separate subgraphs for API calls made during the test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Business-friendly summaries&lt;/strong&gt; — plain language descriptions of what each test verifies. No function names, no selector syntax. Just: "A user fills out the create todo form with a title, description, and date, then clicks Create Todo. The form data is sent to the server as a new todo."&lt;/p&gt;

&lt;p&gt;Here is an example of the flowchart generated from the code above:&lt;/p&gt;

&lt;p&gt;The result is a &lt;code&gt;.flows.md&lt;/code&gt; file colocated next to each test file, plus a root-level index that gives you a single navigation point across the entire test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Actually Benefits From This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;New developers&lt;/strong&gt; can understand what is covered without reading a single line of test code. On day one, they can open the flow gallery and see the user journeys the team has validated. That is faster onboarding and fewer "wait, is this tested?" conversations in code review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product teams&lt;/strong&gt; finally have visibility into testing. Not a coverage percentage — an actual map of user journeys. When they ask "are we testing the checkout flow?", the answer is a link, not a meeting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QA engineers&lt;/strong&gt; can identify gaps at a glance and verify that what is visually described matches what they expect to be covered. They can spot missing edge cases by looking at the flows rather than reading assertions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;With the TWD AI plugin installed, you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd:test-flow-gallery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. The skill finds your TWD test files, processes them, and writes the &lt;code&gt;.flows.md&lt;/code&gt; files alongside your tests. The root index is placed at a predictable location so you can link to it from your README or project wiki.&lt;/p&gt;

&lt;p&gt;The flowcharts use standard Mermaid syntax, which renders natively on GitHub, GitLab, Notion, and most modern documentation tools. No extra dependencies, no build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete TWD AI Workflow
&lt;/h2&gt;

&lt;p&gt;This skill is the finale of a six-step workflow that takes you from zero to a fully automated, AI-assisted testing practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd:setup&lt;/code&gt;&lt;/strong&gt; — Scaffolds the TWD testing environment in your project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd&lt;/code&gt; (twd skill)&lt;/strong&gt; — AI agent that writes and runs in-browser tests against live components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd:ci-setup&lt;/code&gt;&lt;/strong&gt; — Wires your tests into CI/CD with the headless runner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd:test-gaps&lt;/code&gt;&lt;/strong&gt; — Identifies untested user flows and generates missing tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd:test-quality&lt;/code&gt;&lt;/strong&gt; — Reviews your tests for reliability, false positives, and maintenance burden&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/twd:test-flow-gallery&lt;/code&gt;&lt;/strong&gt; — Turns your test suite into visual documentation for the whole team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step builds on the last. The result is a test suite that is not just green in CI — it is legible, maintainable, and understood by everyone who needs to understand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The TWD AI plugin is open source and available at &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;github.com/BRIKEV/twd-ai&lt;/a&gt;. The full TWD documentation, including the philosophy behind test-while-developing, is at &lt;a href="https://twd.dev" rel="noopener noreferrer"&gt;twd.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have been following this series, you now have the full picture. If you are coming to this article first — the rest of the series walks through each step in detail. Start at the beginning and build the workflow incrementally.&lt;/p&gt;

&lt;p&gt;Testing should not be a black box. Your team deserves to see what is covered.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Tests Pass. But Are They Good? Grading Test Quality with /twd:test-quality</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:49:29 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/your-tests-pass-but-are-they-good-grading-test-quality-with-twdtest-quality-2077</link>
      <guid>https://forem.com/kevinccbsg/your-tests-pass-but-are-they-good-grading-test-quality-with-twdtest-quality-2077</guid>
      <description>&lt;h2&gt;
  
  
  The Problem With "We Have Tests"
&lt;/h2&gt;

&lt;p&gt;There is a moment in every project where someone says "we have tests" like it settles the matter. The CI pipeline is green. The coverage number is somewhere north of 70%. Everything is fine.&lt;/p&gt;

&lt;p&gt;Until a bug slips through. Not because the tests failed — but because they never really covered what broke.&lt;/p&gt;

&lt;p&gt;This is the gap between having tests and having good tests. A test that checks whether a button is visible tells you almost nothing about whether your application works. A test that checks whether clicking "Submit" fires the right API call with the right payload — that test is doing real work.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/twd:test-quality&lt;/code&gt; skill is built for exactly this problem. It reads your existing test files, grades them across four weighted dimensions, and hands you a concrete list of what to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Gets Graded
&lt;/h2&gt;

&lt;p&gt;Every test file gets scored across four dimensions. Each one targets a distinct failure mode in how developers tend to write tests under time pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Journey Coverage (35%)&lt;/strong&gt; — This is the heaviest dimension, and for good reason. A test suite full of isolated "does X render?" checks does not tell you whether the user can actually complete a task. Journey coverage looks for complete workflows: does the test cover the sequence of actions a user would take to accomplish something, or does it stop after the first visible element?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interaction Depth (20%)&lt;/strong&gt; — Variety matters. If all your tests do the same kind of interaction — say, only clicking buttons — you are missing a significant portion of how real users engage with your UI. This dimension checks for the range of input types and interaction patterns exercised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assertion Quality (25%)&lt;/strong&gt; — This is where most test suites quietly fail. Assertions that check CSS classes or element visibility feel like verification, but they do not confirm that your application's logic is correct. Strong assertions check actual outcomes: API payloads, state changes, content that results from a specific action. Loose assertions let bugs pass silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error and Edge Cases (20%)&lt;/strong&gt; — The happy path is always tested. What about the unhappy path? Empty states, boundary values, API failures, form validation — these are the scenarios that surface in production and are almost never covered by a first-pass test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Skill Works
&lt;/h2&gt;

&lt;p&gt;Point the skill at your test directory and it will evaluate each file independently. The output is direct: a letter grade (A through D), a weighted overall score, and — for anything below an A — two or three specific, actionable suggestions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd:test-quality src/tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical output might look 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;invoice-form.test.js — C (62/100)

Journey Coverage: D — Tests check that fields render, but no test submits
the form and verifies the result.

Assertion Quality: C — Assertions rely on element visibility. No tests
verify the POST payload or the success state.

Suggestions:
1. Add a test that fills the form and submits it, then asserts the API
   received the correct invoice payload.
2. Add a test for the error state when the API returns a 422.
3. Verify the confirmation message content, not just its presence.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The score is not the point. The suggestions are.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Analysis to Action
&lt;/h2&gt;

&lt;p&gt;Once you have the quality report, the workflow is immediate. You run &lt;code&gt;/twd&lt;/code&gt; on the same files — the core TWD test-writing skill — and it uses the suggestions as its implementation brief.&lt;/p&gt;

&lt;p&gt;The quality skill diagnoses. The test skill fixes. You do not have to manually translate "assertion quality is weak" into new test code — that handoff happens automatically.&lt;/p&gt;

&lt;p&gt;This is the pattern that makes AI-assisted testing practical rather than cosmetic. The AI is not writing tests from scratch based on a vague request. It is working from a structured diagnosis of what is actually missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Here is what a realistic before-and-after looks like for a form component:&lt;/p&gt;

&lt;p&gt;Before — a typical first-pass test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders the submit button&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invoices/new&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;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/submit/i&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&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;After — the same component, improved by the quality feedback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submits a valid invoice and shows confirmation&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createInvoice&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inv_001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invoices/new&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/amount/i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/submit/i&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;rule&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createInvoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EUR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/invoice created/i&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 second test is not dramatically more complex. It is just more intentional. It verifies behavior, not presence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/twd:test-quality&lt;/code&gt; skill is part of the &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;TWD AI plugin for Claude Code&lt;/a&gt;. If you have the plugin installed, you can run a quality audit on any test directory immediately.&lt;/p&gt;

&lt;p&gt;Start with your most critical feature area. Look at what the grader flags as weak on assertion quality and journey coverage — those two dimensions are usually where the highest-value improvements are hiding.&lt;/p&gt;

&lt;p&gt;The tests that catch bugs in production are not the ones you wrote fastest. They are the ones that actually exercise the path that breaks.&lt;/p&gt;




&lt;p&gt;Next in the series: the Test Flow Gallery — a curated set of reusable test patterns for common UI scenarios, so you are not writing from scratch every time.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Tests Are Running — But Are They Covering the Right Things?</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:49:04 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/your-tests-are-running-but-are-they-covering-the-right-things-55hc</link>
      <guid>https://forem.com/kevinccbsg/your-tests-are-running-but-are-they-covering-the-right-things-55hc</guid>
      <description>&lt;p&gt;You've wired up your test suite. CI is green. You're shipping. And then a bug lands in production on a route nobody thought to test.&lt;/p&gt;

&lt;p&gt;It's not that your tests are bad. It's that you didn't know what was missing.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;code&gt;/twd:test-gaps&lt;/code&gt; is built to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between "tests exist" and "tests are enough"
&lt;/h2&gt;

&lt;p&gt;Most coverage tools tell you about lines and branches. What they don't tell you is: which user-facing routes have zero test coverage? Which pages have a test that visits them but never clicks anything, never submits a form, never triggers a mutation?&lt;/p&gt;

&lt;p&gt;There's a difference between a test that loads a page and a test that actually exercises it. A route can show up as "covered" while the core interaction — the form submission, the delete confirmation, the role-based redirect — is completely untested.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/twd:test-gaps&lt;/code&gt; makes that distinction explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the skill actually does
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;/twd:test-gaps&lt;/code&gt;, the &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;TWD plugin&lt;/a&gt; does three things in sequence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route discovery.&lt;/strong&gt; It reads your project and finds every route your app exposes. It doesn't require a specific framework — it detects routes from router config files, page component patterns, and URLs referenced in existing test files. Angular, React Router, Vue Router, SolidJS: it handles all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage classification.&lt;/strong&gt; For each discovered route, it checks the test files and assigns one of three states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tested          — has twd.visit() + userEvent interactions (clicks, inputs, submits)
Partially tested — has twd.visit() but missing interaction or mutation coverage
Untested         — no test file references this route at all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The partially-tested category is where this gets valuable. These are routes that feel covered but aren't. A smoke test that visits &lt;code&gt;/settings&lt;/code&gt; and checks the heading renders is not the same as a test that changes a password, hits submit, and verifies the API call was made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk assessment.&lt;/strong&gt; The skill reads your component code and scores each untested or partially-tested route as HIGH, MEDIUM, or LOW risk based on what it finds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HIGH: mutations (form submissions, delete actions, state changes), financial handling, permission checks&lt;/li&gt;
&lt;li&gt;MEDIUM: complex UI interactions, multi-step flows, conditional rendering logic&lt;/li&gt;
&lt;li&gt;LOW: static pages, read-only views, simple display components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output is a prioritized list. You know exactly where the exposure is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;Say you have a &lt;code&gt;/checkout&lt;/code&gt; route. The skill visits the component, sees a form with payment fields and a submit handler that calls an API. It checks your test files — finds a test that visits the route but only asserts that the page renders. No form interaction. No API mock.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/checkout — PARTIALLY TESTED — HIGH RISK
  Missing: form submission, mutation mock for POST /api/orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the information you need to act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  From gap to green
&lt;/h2&gt;

&lt;p&gt;Once you have the report, the next step is straightforward: run &lt;code&gt;/twd&lt;/code&gt; on the high-priority routes and let the agent write the missing tests.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/twd:test-gaps&lt;/code&gt; to get the prioritized gap report&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/twd&lt;/code&gt; on HIGH-risk untested or partially-tested routes&lt;/li&gt;
&lt;li&gt;Tests are written, run, and fixed until green&lt;/li&gt;
&lt;li&gt;Repeat for MEDIUM-risk routes as capacity allows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You're not guessing anymore. You have a concrete list, sorted by risk, and a tool that can write the tests you point it at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than line coverage
&lt;/h2&gt;

&lt;p&gt;Line coverage tells you what code ran during your tests. It doesn't tell you whether the right things were asserted, whether the interactions were real, or whether the routes that users actually care about are exercised.&lt;/p&gt;

&lt;p&gt;A codebase can have 85% line coverage and a completely untested checkout flow.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/twd:test-gaps&lt;/code&gt; focuses on user-facing behavior: the routes, the interactions, the mutations. It asks the question your users would ask — "what happens when I do this?" — and finds the places where no one has answered it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next article in this series covers &lt;code&gt;/twd:test-quality&lt;/code&gt; — a skill that goes beyond gap detection and grades the quality of your existing tests. Not just whether a route is tested, but whether the test is actually asserting the right things, using the right patterns, and giving you confidence you can rely on.&lt;/p&gt;

&lt;p&gt;If you're curious about the TWD plugin and want to try it yourself, the full source is at &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;https://github.com/BRIKEV/twd-ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Local Tests to CI in One Command</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:47:48 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/from-local-tests-to-ci-in-one-command-p9e</link>
      <guid>https://forem.com/kevinccbsg/from-local-tests-to-ci-in-one-command-p9e</guid>
      <description>&lt;h2&gt;
  
  
  Why Set Up CI Early
&lt;/h2&gt;

&lt;p&gt;Once you have tests worth running, the natural next step is making sure they run on every push. The sooner you do it, the fewer surprises down the road — things like env vars that only exist locally or paths that work on your machine but nowhere else.&lt;/p&gt;

&lt;p&gt;If you've been following this series, you just ran &lt;code&gt;/twd&lt;/code&gt; to write your first batch of tests. This is the right moment to wire them into CI, while everything is fresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Skill Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/twd:ci-setup&lt;/code&gt; is part of the &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;TWD AI plugin&lt;/a&gt;. Like &lt;code&gt;/twd:setup&lt;/code&gt;, it starts with a &lt;strong&gt;discovery phase&lt;/strong&gt; — detecting your project configuration before asking you anything.&lt;/p&gt;

&lt;p&gt;The skill inspects your project to figure out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your framework and build tooling&lt;/li&gt;
&lt;li&gt;The dev server port your app runs on&lt;/li&gt;
&lt;li&gt;Your base path, if you have one configured&lt;/li&gt;
&lt;li&gt;Whether you have coverage already wired up, and if not, whether you want it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of this is inferred automatically. The main thing it asks you about is coverage. Once it has the full picture, it generates a GitHub Actions workflow tailored to your project.&lt;/p&gt;

&lt;p&gt;Here is an example of what it produces:&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;TWD Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;  &lt;span class="c1"&gt;# only needed if using contract-report&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;test&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@v5&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@v5&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;Install dependencies&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 ci&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;Install mock service worker&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;npx twd-js init public --save&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;Start dev server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;nohup npm run dev &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/span&gt;
          &lt;span class="s"&gt;npx wait-on http://localhost:5173&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 TWD tests&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;BRIKEV/twd-cli/.github/actions/run@main&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;contract-report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow starts the dev server, waits for it to be ready, and runs your TWD tests using the official &lt;code&gt;twd-cli&lt;/code&gt; GitHub Action. That action handles Puppeteer setup, Chrome installation, and optionally posts a contract validation report as a PR comment. If your app runs on a different port or base path, the skill adjusts the configuration accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;After you've written tests with &lt;code&gt;/twd&lt;/code&gt;, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd:ci-setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill will work through the detection phase, ask whether you want coverage reporting, then drop the workflow file into &lt;code&gt;.github/workflows/&lt;/code&gt;. Push the branch, open a pull request, and your tests run.&lt;/p&gt;

&lt;p&gt;That first CI run tends to surface the usual suspects: an env var that exists locally but not in CI, a path assumption that only works on your machine, or a dependency that wasn't properly declared. The skill takes care of the pipeline setup — and CI itself takes care of showing you what was missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern This Follows
&lt;/h2&gt;

&lt;p&gt;If you used &lt;code&gt;/twd:setup&lt;/code&gt; to initialize TWD in your project, this feels familiar. Detection first, minimal questions, concrete output. The philosophy is the same: don't make developers manage configuration when the tooling can figure it out.&lt;/p&gt;

&lt;p&gt;The two commands are designed to be used in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/twd&lt;/code&gt; — write tests for your components and pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/twd:ci-setup&lt;/code&gt; — make sure those tests run automatically on every push&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither requires you to know the internals of TWD, GitHub Actions syntax, or coverage tooling. You describe what you want; the skill handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;Setting up CI is about making sure your tests run. The next question is a harder one: are you testing the right things? You might have solid coverage on the happy path and nothing on the edge cases that actually break in production.&lt;/p&gt;

&lt;p&gt;The next article in this series covers the TWD Test Gap Analysis skill — how it analyzes your existing tests, identifies what is missing, and suggests what to write next. Not just "you have low coverage on this file", but a concrete read of what user flows and failure modes are not being tested.&lt;/p&gt;

&lt;p&gt;If you want to follow along or explore the TWD plugin: &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;https://github.com/BRIKEV/twd-ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your AI Doesn't Just Write Tests. It Runs Them Too.</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:47:24 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/your-ai-doesnt-just-write-tests-it-runs-them-too-1a5b</link>
      <guid>https://forem.com/kevinccbsg/your-ai-doesnt-just-write-tests-it-runs-them-too-1a5b</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/kevinccbsg/stop-letting-ai-write-untestable-code-add-determinism-back-with-twd-3a02"&gt;previous article&lt;/a&gt;, we talked about &lt;code&gt;/twd:setup&lt;/code&gt; — the skill that analyzes your project and generates &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt;, a file that teaches your AI agent how tests are structured in your codebase. That was about giving the agent context.&lt;/p&gt;

&lt;p&gt;This one is about what the agent actually does with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Tests That Run in Your Real Browser
&lt;/h2&gt;

&lt;p&gt;Most AI-generated tests run in Node.js — against jsdom, a simulated DOM. That works for a lot of things, but it is not the same as running inside your actual application with real components, real routing, and real network mocks in place.&lt;/p&gt;

&lt;p&gt;TWD tests run in the browser. They execute inside your running app, against the real DOM, with your actual component tree mounted. And the &lt;code&gt;/twd&lt;/code&gt; skill — part of the &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;TWD AI plugin&lt;/a&gt; — takes that a step further. It writes the tests, runs them in your browser, reads the results, and if something fails, it fixes the code and runs again.&lt;/p&gt;

&lt;p&gt;Here's the full cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write&lt;/strong&gt; — the agent reads your &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; to understand your project's test conventions, then generates a test that follows those patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; — the test is sent to your browser via &lt;code&gt;twd-relay&lt;/code&gt;, a WebSocket relay that connects the agent to the TWD sidebar running in your app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read results&lt;/strong&gt; — pass/fail status comes back as plain text with error details (no screenshots, no DOM dumps — just the signal you need)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix and re-run&lt;/strong&gt; — if a test fails, the agent reads the error, adjusts the test, and re-executes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This loop runs automatically. You're not involved until it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Relay Works (Without Getting Heavy)
&lt;/h2&gt;

&lt;p&gt;One thing that surprised me about this setup is how lightweight it is.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;twd-relay&lt;/code&gt; uses a WebSocket connection between the agent and the browser. When the agent wants to run tests, it sends a command through the relay. The browser executes the tests inside the running app — against the real DOM, with your mocked API responses in place, and the real component state.&lt;/p&gt;

&lt;p&gt;Results come back as text. Not screenshots. Not serialized DOM trees. Just: did it pass, and if not, what was the error message.&lt;/p&gt;

&lt;p&gt;This keeps token usage remarkably low. The agent is essentially getting the same output you'd see in a terminal — concise, structured, actionable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It Before You Build
&lt;/h2&gt;

&lt;p&gt;The recommended workflow is test-first. Run &lt;code&gt;/twd&lt;/code&gt; before you implement a feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd Write a test for the checkout form — it should verify that submitting with an empty email shows a validation error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent writes the test based on your existing patterns, runs it in the browser, and it fails — because the feature doesn't exist yet. That's expected. The test is now a specification.&lt;/p&gt;

&lt;p&gt;You implement the feature. The test passes. You didn't have to think about test structure, selectors, or mock setup. The agent handled that using the patterns it already knows from your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When a Test Won't Pass
&lt;/h2&gt;

&lt;p&gt;Not every test is fixable on the first attempt. Sometimes the agent hits a case it can't resolve — a component that behaves differently than expected, or a pattern that doesn't quite translate.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/twd&lt;/code&gt; skill handles this with a hard limit: if a test still fails after three fix attempts, it's marked as &lt;code&gt;it.skip&lt;/code&gt; and left in the file with a comment. It doesn't block the rest of the test run. You can come back to it, investigate the real issue, and decide how to handle it.&lt;/p&gt;

&lt;p&gt;This is important for trust. An agent that quietly hides failures is dangerous. One that skips and surfaces them is honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Your Conversation Clean
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/twd&lt;/code&gt; skill runs in a forked context — meaning the test iterations, failed attempts, and fix cycles happen separately from your main conversation. When it finishes, you get a summary of what passed, what was skipped, and what files were created. You don't have to scroll through 30 messages of debugging to see the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concrete Example
&lt;/h2&gt;

&lt;p&gt;Say you're building a Vue component that fetches and displays user data. You invoke:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd Write tests for the UserProfile component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent reads &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; — it knows your project conventions, how to mock API endpoints with &lt;code&gt;twd.mockRequest()&lt;/code&gt;, and which selectors to use with &lt;code&gt;screenDom&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It generates tests that mock the &lt;code&gt;/api/users/:id&lt;/code&gt; endpoint, visit the page, and assert the displayed data&lt;/li&gt;
&lt;li&gt;It runs them in your browser via the relay&lt;/li&gt;
&lt;li&gt;One test fails — the agent used a selector that doesn't match your actual markup&lt;/li&gt;
&lt;li&gt;It reads the error, corrects the query, re-runs&lt;/li&gt;
&lt;li&gt;All tests pass&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total time: less than two minutes. No manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Actually Need to Get Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The TWD sidebar running in your app (from &lt;code&gt;twd-js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;twd-relay&lt;/code&gt; running locally&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; generated by &lt;code&gt;/twd:setup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/twd&lt;/code&gt; skill installed from &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;twd-ai&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. The relay handles the browser connection, the patterns file handles the conventions, and the skill handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Next: CI Setup
&lt;/h2&gt;

&lt;p&gt;Writing and running tests locally is one half of the equation. The other half is making them part of your CI pipeline — so tests run headlessly on every push, without a browser in sight.&lt;/p&gt;

&lt;p&gt;The next article in this series covers &lt;code&gt;/twd:ci-setup&lt;/code&gt;, which configures your project to run TWD tests in CI using the headless CLI runner. If you've ever wanted your AI-written tests to gate a deployment, that's the one you want.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Letting AI Write Untestable Code. Add Determinism Back with TWD</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Tue, 17 Mar 2026 22:35:17 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/stop-letting-ai-write-untestable-code-add-determinism-back-with-twd-3a02</link>
      <guid>https://forem.com/kevinccbsg/stop-letting-ai-write-untestable-code-add-determinism-back-with-twd-3a02</guid>
      <description>&lt;p&gt;Now that AI is generating a big portion of our frontend code, speed is no longer the main bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve seen this before: when teams wanted to move fast, the real enabler wasn’t “more code” — it was &lt;strong&gt;having a solid testing strategy&lt;/strong&gt; that allowed safe refactoring and iteration.&lt;/p&gt;

&lt;p&gt;That hasn’t changed. If anything, it’s &lt;strong&gt;more important now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In previous posts, I talked about the TWD philosophy, but one idea is key here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Test what you own. Mock what you don’t.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this mindset, &lt;strong&gt;TWD (Test While Developing)&lt;/strong&gt; lets you create &lt;strong&gt;deterministic UI tests&lt;/strong&gt;, where you fully control the environment, simulate any scenario, and avoid flaky behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Allow AI Know Your Project
&lt;/h2&gt;

&lt;p&gt;AI can write tests… but not your tests.&lt;/p&gt;

&lt;p&gt;It doesn’t know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how your project is structured&lt;/li&gt;
&lt;li&gt;what should be mocked&lt;/li&gt;
&lt;li&gt;how your auth works&lt;/li&gt;
&lt;li&gt;what “good tests” look like in your codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even if it generates tests, they often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don’t follow your patterns&lt;/li&gt;
&lt;li&gt;mock the wrong things&lt;/li&gt;
&lt;li&gt;miss important flows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: TWD + Skills
&lt;/h2&gt;

&lt;p&gt;To solve this, we introduced TWD skills inside a &lt;a href="https://github.com/BRIKEV/twd-ai" rel="noopener noreferrer"&gt;Claude Code plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These skills give the AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context about your project&lt;/li&gt;
&lt;li&gt;rules to follow&lt;/li&gt;
&lt;li&gt;patterns to reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of generic tests, you get tests that actually fit your codebase.&lt;/p&gt;




&lt;h2&gt;
  
  
  /twd:setup — The Most Important First Step
&lt;/h2&gt;

&lt;p&gt;Everything starts here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is an interactive setup that analyzes your project and creates:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;.claude/twd-patterns.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This file becomes the source of truth for how tests should be written.&lt;/p&gt;


&lt;h2&gt;
  
  
  What /twd:setup Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Detects your framework, Vite config, entry points, CSS setup&lt;/li&gt;
&lt;li&gt;Asks about:

&lt;ul&gt;
&lt;li&gt;auth &amp;amp; permissions&lt;/li&gt;
&lt;li&gt;API structure&lt;/li&gt;
&lt;li&gt;third-party dependencies&lt;/li&gt;
&lt;li&gt;state management&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generates a project-specific testing config&lt;/li&gt;
&lt;li&gt;Optionally installs and configures TWD for you&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How It Looks in Practice
&lt;/h2&gt;

&lt;p&gt;You just run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/twd:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And the agent starts understanding your project:&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%2Fupznvqpagu4mcjewn26g.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%2Fupznvqpagu4mcjewn26g.png" alt="prompt using twd:setup skill" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will ask the right questions to understand how your app works and detect your frontend configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should be mocked?&lt;/li&gt;
&lt;li&gt;How does auth behave?&lt;/li&gt;
&lt;li&gt;Where are your services?&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%2Fzuhitpw6qzc6swl9xyf4.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%2Fzuhitpw6qzc6swl9xyf4.png" alt="First questions" width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once completed, it generates your config file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;.claude/twd-patterns.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is &lt;strong&gt;critical&lt;/strong&gt; — every future test uses this as context.&lt;/p&gt;



&lt;p&gt;If something is outdated, it will even suggest improvements:&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%2Fcgj09nb4umm91o51wyoc.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%2Fcgj09nb4umm91o51wyoc.png" alt="Asking to improve current configuration" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;And when it's done, it guides you to the next step:&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%2Fj5lph0uj1832faglw38j.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%2Fj5lph0uj1832faglw38j.png" alt="Message recomendating the next step using the twd-tester skill" width="800" height="76"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Without this step, AI-generated tests are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generic&lt;/li&gt;
&lt;li&gt;inconsistent&lt;/li&gt;
&lt;li&gt;sometimes useless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;/twd:setup&lt;/code&gt;, tests become aligned with your architecture.&lt;/p&gt;

&lt;p&gt;The plugin also includes skills for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automatic test generation&lt;/li&gt;
&lt;li&gt;CI setup&lt;/li&gt;
&lt;li&gt;test quality analysis&lt;/li&gt;
&lt;li&gt;test gap detection&lt;/li&gt;
&lt;li&gt;flow visualisation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll cover those in future posts.&lt;/p&gt;


&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;AI can make you faster.&lt;/p&gt;

&lt;p&gt;But tests are what let you keep that speed without breaking everything.&lt;/p&gt;

&lt;p&gt;If you care about frontend quality in the AI era, don’t just generate code:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate confidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Agent conversation:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
      
      
      
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
  

&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;TWD setup skill&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;claude-opus-4-6&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;&amp;lt;command-message&amp;gt;twd:setup&amp;lt;/command-message&amp;gt;&lt;br&gt;
&amp;lt;command-name&amp;gt;/twd:setup&amp;lt;/command-name&amp;gt;&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Base directory for this skill: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;/.claude/plugins/cache/twd-ai/twd/1.1.0/skills/setup&lt;/p&gt;

&lt;h1&gt;TWD Project Setup&lt;/h1&gt;

&lt;p&gt;You are configuring TWD (Test While Developing) for this project. Your job is to detect project settings, ask questions for what can't be auto-detected, and generate a &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; configuration file.&lt;/p&gt;

&lt;h2&gt;Step 1: Auto-Detect Project Settings&lt;/h2&gt;

&lt;p&gt;Read these files to pre-fill answers (read all in parallel):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt; — detect framework from dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;react&lt;/code&gt; / &lt;code&gt;react-dom&lt;/code&gt; → React&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vue&lt;/code&gt; → Vue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@angular/core&lt;/code&gt; → Angular&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;solid-js&lt;/code&gt; → Solid&lt;/li&gt;
&lt;li&gt;Also detect CSS/component libraries: &lt;code&gt;@mui/material&lt;/code&gt;, &lt;code&gt;@chakra-ui/react&lt;/code&gt;, &lt;code&gt;antd&lt;/code&gt;, &lt;code&gt;@mantine/core&lt;/code&gt;, &lt;code&gt;vuetify&lt;/code&gt;, &lt;code&gt;primevue&lt;/code&gt;, &lt;code&gt;element-plus&lt;/code&gt;, &lt;code&gt;@angular/material&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;vite.config.ts&lt;/code&gt;&lt;/strong&gt; (or &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.mjs&lt;/code&gt;) — detect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;base&lt;/code&gt; field (Vite base path, default &lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server.port&lt;/code&gt; (dev server port, default &lt;code&gt;5173&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt; — detect entry point from &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; src attribute&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Glob for &lt;code&gt;src/services/&lt;/code&gt;, &lt;code&gt;src/api/&lt;/code&gt;, &lt;code&gt;src/lib/api&lt;/code&gt;&lt;/strong&gt; — detect API/services folder&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check if &lt;code&gt;public/&lt;/code&gt; directory exists&lt;/strong&gt; — confirm public folder name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Detect state management&lt;/strong&gt; from &lt;code&gt;package.json&lt;/code&gt; dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;zustand&lt;/code&gt; → Zustand&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@reduxjs/toolkit&lt;/code&gt; or &lt;code&gt;redux&lt;/code&gt; → Redux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jotai&lt;/code&gt; → Jotai&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pinia&lt;/code&gt; → Pinia&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check if &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; already exists&lt;/strong&gt; — offer to update vs overwrite&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Step 2: Ask Questions&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT: Use the &lt;code&gt;AskUserQuestion&lt;/code&gt; tool for ALL questions.&lt;/strong&gt; This provides an interactive UI experience. Never dump questions as a plain numbered list in text output.&lt;/p&gt;

&lt;p&gt;Present auto-detected values as a summary first, then ask questions in two batches:&lt;/p&gt;

&lt;h3&gt;Batch 1: Project basics (confirm auto-detected values)&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do NOT ask individual questions for values you already detected.&lt;/strong&gt; Show a single summary of all detected values and ask "Does anything look wrong?" using &lt;code&gt;AskUserQuestion&lt;/code&gt;. The user only needs to respond if something is incorrect. Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here's what I detected:&lt;br&gt;
- Framework: React&lt;br&gt;
- Vite base path: &lt;code&gt;/&lt;/code&gt;&lt;br&gt;
- Dev server port: &lt;code&gt;5173&lt;/code&gt;&lt;br&gt;
- Entry point: &lt;code&gt;src/main.tsx&lt;/code&gt;&lt;br&gt;
- Public folder: &lt;code&gt;public/&lt;/code&gt;&lt;br&gt;
- API services: &lt;code&gt;src/services/&lt;/code&gt;&lt;br&gt;
- CSS library: MUI&lt;br&gt;
- State management: Zustand&lt;/p&gt;

&lt;p&gt;Does anything look wrong, or should I continue?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;Batch 2: Testing concerns (need user input)&lt;/h3&gt;

&lt;p&gt;After confirming batch 1, use &lt;code&gt;AskUserQuestion&lt;/code&gt; for each of these that requires user input:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CSS library docs&lt;/strong&gt; (only if a CSS library was detected): Where are the docs? (URL, local path, or "skip")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth middleware&lt;/strong&gt;: Does your project have route-based auth/permissions? If yes, briefly describe the pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party modules&lt;/strong&gt;: Does your project use external services that need mocking in tests? (e.g., Auth0, Stripe, analytics)

&lt;ul&gt;
&lt;li&gt;If yes: Which modules and how are they imported?&lt;/li&gt;
&lt;li&gt;The agent needs this to know what to Sinon-stub in tests — "test what you own, mock what you don't"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State reset&lt;/strong&gt; (only if state management was detected): How do you reset the store? (e.g., &lt;code&gt;useStore.setState(initialState)&lt;/code&gt;, &lt;code&gt;store.$reset()&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;TWD runs without page reloads — store state persists between tests and must be reset in beforeEach&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Step 3: Generate &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Create the &lt;code&gt;.claude/&lt;/code&gt; directory if it doesn't exist, then write &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt; with the following sections. &lt;strong&gt;Only include sections that are relevant&lt;/strong&gt; — omit sections that don't apply.&lt;/p&gt;

&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# TWD Project Patterns&lt;/span&gt;

&lt;span class="gu"&gt;## Project Configuration&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Framework**&lt;/span&gt;: FRAMEWORK
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Vite base path**&lt;/span&gt;: BASE_PATH
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Dev server port**&lt;/span&gt;: PORT
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Entry point**&lt;/span&gt;: ENTRY_FILE
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Public folder**&lt;/span&gt;: PUBLIC_DIR

&lt;span class="gu"&gt;### Relay Commands&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# Run all tests (default — use this if base path is / and port is 5173)&lt;/span&gt;
npx twd-relay run

&lt;span class="c"&gt;# Run all tests (custom config)&lt;/span&gt;
npx twd-relay run &lt;span class="nt"&gt;--port&lt;/span&gt; PORT &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="s2"&gt;"BASE_PATH__twd/ws"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Standard Imports&lt;/h2&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenDom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twd-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twd-js/runner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Project-specific imports go here (added by user)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Visit Paths&lt;/h2&gt;

&lt;p&gt;All &lt;code&gt;twd.visit()&lt;/code&gt; calls must include the base path prefix:&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&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;BASE_PATH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&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;BASE_PATHsome-page&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;h2&gt;Standard beforeEach / afterEach&lt;/h2&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRequestMockRules&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearComponentMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// STORE_RESET (if applicable — e.g., useStore.setState(initialState), store.$reset())&lt;/span&gt;
  &lt;span class="c1"&gt;// AUTH_SETUP (if applicable)&lt;/span&gt;
  &lt;span class="c1"&gt;// THIRD_PARTY_STUBS (if applicable — e.g., Sinon.stub(authModule, 'useAuth').returns(...))&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRequestMockRules&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;h2&gt;API Service Types&lt;/h2&gt;

&lt;p&gt;Service/API types are located in: &lt;code&gt;API_FOLDER&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Read files in this folder to understand endpoint URLs and response shapes when writing mock data.&lt;/p&gt;

&lt;h2&gt;CSS / Component Library&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Library&lt;/strong&gt;: CSS_LIB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: CSS_DOCS_LOCATION&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When writing tests, refer to library docs for correct ARIA roles and component structure.&lt;/p&gt;

&lt;h2&gt;Auth Middleware&lt;/h2&gt;

&lt;p&gt;AUTH_DESCRIPTION&lt;/p&gt;

&lt;h3&gt;Route → Permission Mapping&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Required Permissions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(to be filled by developer)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;Third-Party Modules&lt;/h2&gt;

&lt;p&gt;"Test what you own, mock what you don't." These external modules should be stubbed in tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Import Pattern&lt;/th&gt;
&lt;th&gt;Stub Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MODULE_NAME&lt;/td&gt;
&lt;td&gt;&lt;code&gt;import { hook } from 'package'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sinon.stub(moduleObj, 'hook').returns(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(to be filled by developer)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;See the test-writing reference for the default-export object pattern required for ESM stubbing.&lt;/p&gt;

&lt;h2&gt;Portals and Dialogs&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;screenDomGlobal&lt;/code&gt; instead of &lt;code&gt;screenDom&lt;/code&gt; for elements rendered in portals (modals, dropdowns, tooltips):&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;screenDomGlobal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twd-js&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;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screenDomGlobal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&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;pre class="highlight"&gt;&lt;code&gt;
### Template rules:
- If base path is `/`, simplify visit paths to just `await twd.visit("/page")`
- If port is `5173` and base path is `/`, use `npx twd-relay run` (no flags)
- Omit the "Auth Middleware" section entirely if no auth
- Omit the "Third-Party Modules" section entirely if no external modules
- Omit the "CSS / Component Library" section if none detected
- Omit the "API Service Types" section if no services folder found
- Omit the `STORE_RESET` comment in beforeEach if no state management library
- Omit the `AUTH_SETUP` comment in beforeEach if no auth middleware
- Omit the `THIRD_PARTY_STUBS` comment in beforeEach if no third-party modules

## Step 4: Optionally Run Setup

After generating the config file, check if TWD is already installed. If not, ask the user if they want to run setup now:

1. `npm install twd-js`
2. `npm install --save-dev twd-relay`
3. `npx twd-js init PUBLIC_DIR --save`
4. Configure entry point — **insert this DEV block BEFORE the existing app mount code** (before `createRoot`, `createApp`, etc.). The import is **`twd-js/bundled`**, NOT `twd-js`:

```typescript
if (import.meta.env.DEV) {
  const { initTWD } = await import('twd-js/bundled');
  const tests = import.meta.glob("./**/*.twd.test.ts");

  initTWD(tests, {
    open: true,
    position: 'left',
    serviceWorker: true,
    serviceWorkerUrl: '/mock-sw.js',
  });

  // Connect twd-relay browser client
  const { createBrowserClient } = await import('twd-relay/browser');
  const client = createBrowserClient({ url: `${window.location.origin}/__twd/ws` });
  client.connect();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adjustments&lt;/strong&gt;: If Vite &lt;code&gt;base&lt;/code&gt; is not &lt;code&gt;/&lt;/code&gt;, update &lt;code&gt;serviceWorkerUrl&lt;/code&gt; to &lt;code&gt;'/BASE/mock-sw.js'&lt;/code&gt; and relay URL to &lt;code&gt;`${window.location.origin}/BASE/__twd/ws`&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Add Vite plugins:&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twdHmr&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twd-js/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twdRemote&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twd-relay/vite&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PluginOption&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Add to plugins array:&lt;/span&gt;
&lt;span class="nl"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other plugins&lt;/span&gt;
  &lt;span class="nf"&gt;twdHmr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;twdRemote&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PluginOption&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;ol&gt;
&lt;li&gt;Write a first test file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only run steps the user approves. Show what each step does before executing.&lt;/p&gt;

&lt;h2&gt;Output&lt;/h2&gt;

&lt;p&gt;When done, summarize:&lt;br&gt;
- Where the config file was written&lt;br&gt;
- What values were detected vs asked&lt;br&gt;
- What setup steps were completed (if any)&lt;br&gt;
- Next steps for the user (e.g., "Run &lt;code&gt;npm run dev&lt;/code&gt; to see the TWD sidebar")&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Setting up TWD for your project. Let me detect your project settings first.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;8 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Here's what I detected from your project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: React 19 + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite base path&lt;/strong&gt;: &lt;code&gt;/&lt;/code&gt; (default)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev server port&lt;/strong&gt;: &lt;code&gt;5173&lt;/code&gt; (default)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entry point&lt;/strong&gt;: &lt;code&gt;src/main.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public folder&lt;/strong&gt;: &lt;code&gt;public/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API services&lt;/strong&gt;: &lt;code&gt;src/api/&lt;/code&gt; (found &lt;code&gt;todos.ts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS&lt;/strong&gt;: Tailwind CSS v4 + Radix UI + shadcn/ui pattern (CVA, clsx, tailwind-merge)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State management&lt;/strong&gt;: Zustand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No auth middleware or third-party modules detected. Let me confirm this with you.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now let me ask about the remaining items that need your input.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Got it. Let me generate the configuration file.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;2 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Config file written. Now let me check if TWD is already installed.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;TWD is not installed yet. Let me ask if you'd like to run the setup.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Let's start with installing the packages and initializing TWD.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;2 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Good. Now let me configure the entry point and Vite plugins.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;2 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now add the Vite plugins.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now let me read the API file to write a meaningful first test.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;4 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now let me create a first test file for the Helloworld page.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;1 message not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;TWD setup is complete. Here's a summary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config file&lt;/strong&gt;: &lt;code&gt;.claude/twd-patterns.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-detected values&lt;/strong&gt;: React 19, Vite defaults (&lt;code&gt;/&lt;/code&gt;, &lt;code&gt;5173&lt;/code&gt;), &lt;code&gt;src/main.tsx&lt;/code&gt;, &lt;code&gt;public/&lt;/code&gt;, &lt;code&gt;src/api/&lt;/code&gt;, Tailwind + Radix UI, Zustand&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup steps completed&lt;/strong&gt;:&lt;br&gt;
1. Installed &lt;code&gt;twd-js&lt;/code&gt; and &lt;code&gt;twd-relay&lt;/code&gt;&lt;br&gt;
2. Initialized service worker in &lt;code&gt;public/mock-sw.js&lt;/code&gt;&lt;br&gt;
3. Added TWD dev block to &lt;code&gt;src/main.tsx&lt;/code&gt; (before app mount)&lt;br&gt;
4. Added &lt;code&gt;twdHmr()&lt;/code&gt; and &lt;code&gt;twdRemote()&lt;/code&gt; Vite plugins to &lt;code&gt;vite.config.ts&lt;/code&gt;&lt;br&gt;
5. Created first test: &lt;code&gt;src/pages/Helloworld/Helloworld.twd.test.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next steps&lt;/strong&gt;:&lt;br&gt;
- Run &lt;code&gt;npm run dev&lt;/code&gt; to start the dev server — you'll see the TWD sidebar on the left&lt;br&gt;
- Run &lt;code&gt;npx twd-relay run&lt;/code&gt; in a separate terminal to execute tests from CLI&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        14 of 38 messages
          · some content was hidden by the author
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>testing</category>
      <category>twd</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Are Developers Still Learning, or Just Using AI?</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Tue, 10 Mar 2026 22:10:52 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/are-developers-still-learning-or-just-using-ai-53dk</link>
      <guid>https://forem.com/kevinccbsg/are-developers-still-learning-or-just-using-ai-53dk</guid>
      <description>&lt;p&gt;AI is changing the way we work as developers.&lt;/p&gt;

&lt;p&gt;We are moving from writing code to &lt;strong&gt;writing specifications&lt;/strong&gt;. Instead of implementing features ourselves, we rely more on &lt;strong&gt;agent modes&lt;/strong&gt; and tools like Claude Code, Codex, or Gemini CLI.&lt;/p&gt;

&lt;p&gt;This works incredibly well. Productivity increases, repetitive work disappears, and we can ship features faster.&lt;/p&gt;

&lt;p&gt;But there is something I’ve started noticing.&lt;/p&gt;

&lt;p&gt;Developers are not necessarily learning more.&lt;br&gt;
We are just &lt;strong&gt;doing the same things faster&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
   AI Cannot Replace Understanding
&lt;/h2&gt;

&lt;p&gt;When you rely heavily on AI tools, something subtle happens.&lt;/p&gt;

&lt;p&gt;You stop exploring new solutions yourself.&lt;br&gt;
You stop experimenting.&lt;br&gt;
You stop learning new paradigms.&lt;/p&gt;

&lt;p&gt;Many developers simply apply the same solutions they always used, just faster because AI writes the code.&lt;/p&gt;

&lt;p&gt;When a new approach or development practice appears, it often does not get adopted well because the developer does not really understand it.&lt;/p&gt;

&lt;p&gt;AI can generate code.&lt;/p&gt;

&lt;p&gt;But it cannot give you &lt;strong&gt;knowledge you never built&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Knowledge Gap
&lt;/h2&gt;

&lt;p&gt;A simple example.&lt;/p&gt;

&lt;p&gt;I am not an expert in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;load testing&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know these areas are important. But if I do not understand the concepts, how am I supposed to apply them correctly?&lt;/p&gt;

&lt;p&gt;Even with the best prompts and agents, the result will still be shallow.&lt;/p&gt;

&lt;p&gt;AI helps execute ideas. It does not replace learning them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Shows the Most
&lt;/h2&gt;

&lt;p&gt;I notice this especially in two areas.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing&lt;/li&gt;
&lt;li&gt;accessibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Recently I have heard things like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why write tests if AI generates almost perfect code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We run Playwright tests with AI after the feature. That is enough.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But testing is not about validating one run. It exists to prevent regressions, catch old bugs, and protect systems as they evolve.&lt;/p&gt;

&lt;p&gt;AI can generate tests.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;testing strategy still requires understanding.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;Accessibility has always been ignored in many projects.&lt;/p&gt;

&lt;p&gt;AI has not changed that.&lt;/p&gt;

&lt;p&gt;You can add an accessibility agent, but accessibility is not just adding attributes. It requires understanding how assistive technologies behave, how screen readers navigate, and how keyboard interaction works.&lt;/p&gt;

&lt;p&gt;Without that knowledge, accessibility stays superficial.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Developer Loop
&lt;/h2&gt;

&lt;p&gt;Something interesting is happening.&lt;/p&gt;

&lt;p&gt;Developers spend more time writing Markdown specs for AI than learning new technical concepts.&lt;/p&gt;

&lt;p&gt;Instead of experimenting, breaking things, and learning through failure, we optimise prompts and agent workflows.&lt;/p&gt;

&lt;p&gt;But not necessarily &lt;strong&gt;ourselves&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Am Also In That Loop
&lt;/h2&gt;

&lt;p&gt;To be fair, I am also part of this.&lt;/p&gt;

&lt;p&gt;AI tools are powerful and it is easy to rely on them.&lt;/p&gt;

&lt;p&gt;But recently I started asking myself a simple question.&lt;/p&gt;

&lt;p&gt;Am I actually improving?&lt;/p&gt;

&lt;p&gt;Or am I just getting faster?&lt;/p&gt;

&lt;p&gt;So I am trying to go back to learning new concepts, experimenting more, and understanding systems deeper.&lt;/p&gt;

&lt;p&gt;And only after that, delegating to AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask Yourself This
&lt;/h2&gt;

&lt;p&gt;If AI makes you &lt;strong&gt;10x more productive&lt;/strong&gt;, ask yourself:&lt;/p&gt;

&lt;p&gt;Are you doing something you would never do if you had infinite time?&lt;/p&gt;

&lt;p&gt;Are you exploring areas you previously did not understand?&lt;/p&gt;

&lt;p&gt;Or are you just shipping the same things faster?&lt;/p&gt;

&lt;p&gt;And one more question.&lt;/p&gt;

&lt;p&gt;If your productivity increases 10x, do you now have 10x more time?&lt;/p&gt;

&lt;p&gt;More time to learn, exercise, spend time with family, or build deeper knowledge?&lt;/p&gt;

&lt;p&gt;If the answer is no, then what is AI really doing for you?&lt;/p&gt;

&lt;p&gt;Is it helping you become a better developer?&lt;/p&gt;

&lt;p&gt;Or just a faster version of the same developer?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>twd</category>
      <category>frontend</category>
    </item>
    <item>
      <title>TWD Tip: Stub Auth0 Hooks and Mock React Modules</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Mon, 12 Jan 2026 22:50:46 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/twd-tip-stub-auth0-hooks-and-mock-react-modules-19b1</link>
      <guid>https://forem.com/kevinccbsg/twd-tip-stub-auth0-hooks-and-mock-react-modules-19b1</guid>
      <description>&lt;p&gt;Authentication is one of the trickiest features to test in modern apps.&lt;/p&gt;

&lt;p&gt;Tools like Auth0 help a lot, especially with React, providing hooks like useAuth0 to manage authentication.&lt;/p&gt;

&lt;p&gt;However, automating these tests is tricky.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With Cypress, testing across domains requires special configuration.&lt;/li&gt;
&lt;li&gt;With Vitest, you need to mock data, but your tests still only return a simple pass/fail in the terminal.&lt;/li&gt;
&lt;li&gt;You also need real users created and validated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://twd.dev/" rel="noopener noreferrer"&gt;TWD (Testing While Developing)&lt;/a&gt; takes a different approach: it encourages testing &lt;strong&gt;behavior&lt;/strong&gt;, not infrastructure. That means you don’t need to test the auth provider itself — you just need to simulate the authentication behavior your app depends on.&lt;/p&gt;

&lt;p&gt;For React apps using Auth0 with PKCE flow, it’s a bit more complicated, because we rely on the hook rather than simple requests. Let’s see how we can fully control authentication in our tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tool for spying and mocking
&lt;/h2&gt;

&lt;p&gt;We’ll use &lt;strong&gt;Sinon&lt;/strong&gt;, a classic library for spies, stubs, and mocks. Why Sinon? Because it works directly in the browser — which is essential for TWD.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the Auth0 hook
&lt;/h2&gt;

&lt;p&gt;Here’s the basic usage in a React component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@auth0/auth0-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// get info of user in the app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth0&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These methods allow your app to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log in and log out users&lt;/li&gt;
&lt;li&gt;Get user info&lt;/li&gt;
&lt;li&gt;Get tokens to send to your API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Move auth logic to a separate file
&lt;/h2&gt;

&lt;p&gt;To stub the hook in TWD tests, we create a small wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@auth0/auth0-react&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;useAuth&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="nf"&gt;useAuth0&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: we export as &lt;strong&gt;default&lt;/strong&gt; and as an &lt;strong&gt;object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because ESModules are immutable by default. Named exports like export const &lt;code&gt;useAuth = ...&lt;/code&gt; &lt;strong&gt;cannot be mocked at runtime&lt;/strong&gt;, but an object property can. This small tradeoff gives us complete control during tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stub the hook in your tests
&lt;/h2&gt;

&lt;p&gt;Now, in your TWD test, you can use Sinon to control authentication behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twd-js/runner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenDom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twd-js&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;authSession&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;../hooks/useAuth&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;Sinon&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;sinon&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;userMock&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;./userMock.json&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth0ContextInterface&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/auth0-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;App tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRequestMockRules&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;afterEach&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;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRequestMockRules&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should render home page for authenticated user&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useAuth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Auth0ContextInterface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;welcomeText&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authenticated area&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;twd&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="nx"&gt;welcomeText&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;infoText&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;screenDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are signed in with Auth0. Manage your profile and jot down quick notes below.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;twd&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="nx"&gt;infoText&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;&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%2Fkefsasmgjwt9civl35lr.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%2Fkefsasmgjwt9civl35lr.png" alt="test passing" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this setup, you can test any scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User is logged in or logged out&lt;/li&gt;
&lt;li&gt;User has different roles or data&lt;/li&gt;
&lt;li&gt;Authentication errors&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Exporting hooks as mutable objects and stubbing them with Sinon lets you run &lt;strong&gt;browser tests&lt;/strong&gt; in TWD with full control over authentication behavior. This allows you to focus on &lt;strong&gt;testing your app’s behavior&lt;/strong&gt;, without worrying about the complexities of the auth provider itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full example React app with this flow: &lt;a href="https://github.com/BRIKEV/twd-auth0-pkce" rel="noopener noreferrer"&gt;twd-auth0-pkce&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Auth flow with backend sessions example: &lt;a href="https://github.com/BRIKEV/twd-auth0" rel="noopener noreferrer"&gt;twd-auth0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official TWD documentation: &lt;a href="https://twd.dev/" rel="noopener noreferrer"&gt;twd.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach isn’t just for Auth0 — it works for any module you need to control in TWD browser tests.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>react</category>
      <category>twd</category>
    </item>
    <item>
      <title>TWD 1.4.x is out – focused on Developer Experience</title>
      <dc:creator>Kevin Julián Martínez Escobar</dc:creator>
      <pubDate>Sun, 11 Jan 2026 17:50:51 +0000</pubDate>
      <link>https://forem.com/kevinccbsg/twd-14x-is-out-focused-on-developer-experience-3fh9</link>
      <guid>https://forem.com/kevinccbsg/twd-14x-is-out-focused-on-developer-experience-3fh9</guid>
      <description>&lt;p&gt;We’ve just released &lt;a href="https://www.npmjs.com/package/twd-js" rel="noopener noreferrer"&gt;&lt;strong&gt;TWD 1.4.x&lt;/strong&gt;&lt;/a&gt;, and this version is all about &lt;strong&gt;improving the developer experience&lt;/strong&gt; while testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s new?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Better testing flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The sidebar now remembers whether it was open or closed using session storage.&lt;/li&gt;
&lt;li&gt;When you reload the page while testing, TWD brings you back to the same test, so you can continue right where you left off.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Custom theming
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can now theme the sidebar to match your product or brand colors.&lt;/li&gt;
&lt;li&gt;This makes TWD feel more integrated into your app instead of a foreign tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Theming docs: &lt;a href="https://twd.dev/theming.html" rel="noopener noreferrer"&gt;https://twd.dev/theming.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New testing examples
&lt;/h2&gt;

&lt;p&gt;We’ve added more real-world examples to show how TWD fits into modern apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React Router framework&lt;/strong&gt; mode testing examples: &lt;a href="https://twd.dev/frameworks.html#react-router-framework-mode" rel="noopener noreferrer"&gt;https://twd.dev/frameworks.html#react-router-framework-mode&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auth0 authorization flow testing: &lt;a href="https://github.com/BRIKEV/twd-auth0" rel="noopener noreferrer"&gt;https://github.com/BRIKEV/twd-auth0&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  shadcn/ui testing guide
&lt;/h2&gt;

&lt;p&gt;If you’re using shadcn/ui, we also published a full guide showing how to test all its components using TWD: &lt;a href="https://brikev.github.io/twd-shadcn/" rel="noopener noreferrer"&gt;https://brikev.github.io/twd-shadcn/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This isn’t a massive release in terms of features, but it includes several UX and DX improvements, plus a refreshed default theme, to make day-to-day testing smoother and more pleasant.&lt;/p&gt;

&lt;p&gt;As always, feedback is welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>twd</category>
    </item>
  </channel>
</rss>
