<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ReceiptRipper]]></title><description><![CDATA[ReceiptRipper]]></description><link>https://receiptripper.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/699c8e0c40e1f055acc4e4c6/64d49193-d7f5-4b8e-a2e9-df6d3daf3775.png</url><title>ReceiptRipper</title><link>https://receiptripper.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 08:38:11 GMT</lastBuildDate><atom:link href="https://receiptripper.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AI Shopped My Store. Math Decided If It Could Ship: Building ReceiptRipper with Passmark]]></title><description><![CDATA[A checkout flow can pass and still lie.
The button can work. The order page can load. The invoice can download. The email can arrive. Every normal end-to-end assertion can look green.
And still, somew]]></description><link>https://receiptripper.hashnode.dev/ai-shopped-my-store-math-decided-if-it-could-ship-building-receiptripper-with-passmark</link><guid isPermaLink="true">https://receiptripper.hashnode.dev/ai-shopped-my-store-math-decided-if-it-could-ship-building-receiptripper-with-passmark</guid><category><![CDATA[breakingappshackathon]]></category><category><![CDATA[passmark]]></category><category><![CDATA[playwright]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Next.js]]></category><dc:creator><![CDATA[Manjunath Patil]]></dc:creator><pubDate>Mon, 11 May 2026 06:44:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/699c8e0c40e1f055acc4e4c6/c1e2db21-3232-4c46-b8ca-d5121cdb4f1a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A checkout flow can pass and still lie.</p>
<p>The button can work. The order page can load. The invoice can download. The email can arrive. Every normal end-to-end assertion can look green.</p>
<p>And still, somewhere in the system, the customer paid ₹1,455.94 while the admin screen says ₹1,356.94 and the invoice says ₹1,455.95.</p>
<p>That is the bug class I wanted to catch for <strong>#breakingappshackathon</strong>.</p>
<p>So I built <strong>ReceiptRipper</strong>: a controlled commerce app plus a Passmark regression gauntlet for checkout truth.</p>
<p>The thesis:</p>
<blockquote>
<p><strong>AI is the shopper. Math is the judge.</strong></p>
</blockquote>
<p>Passmark drives the app like a user. Playwright records the evidence. Redis caches Passmark steps. A Decimal.js oracle computes the expected money truth. ReceiptRipper then compares every place the store makes a money claim:</p>
<pre><code class="language-txt">product page
cart
checkout
confirmation page
email receipt
admin order
order API
invoice PDF
localized UI text
inventory state
</code></pre>
<p>If they agree, the report says:</p>
<pre><code class="language-txt">SHIP
</code></pre>
<p>If they disagree, the report says:</p>
<pre><code class="language-txt">DO NOT SHIP
</code></pre>
<img src="https://raw.githubusercontent.com/ladiesmans217/Breaking-Apps/main/article-assets/mutant-invoice-admin-report.png" alt="ReceiptRipper mutant invoice and admin report" style="display:block;margin:0 auto" />

<h2>The Short Version</h2>
<p>ReceiptRipper is not a generic AI QA dashboard.</p>
<p>It is a domain-specific truth test:</p>
<blockquote>
<p>A store must not tell different money stories in different places.</p>
</blockquote>
<p>The final local verification passed:</p>
<pre><code class="language-txt">Unit tests: 12 passed
Passmark-powered browser journey: 1 passed
Honest truth scenario: 1 passed
Mutant truth scenarios: 7 passed
Full truth Playwright tests: 5 passed
Full truth report rows: 6 generated
Production build: passed
Redis-backed test:full run: passed
</code></pre>
<p>The counts are different on purpose:</p>
<pre><code class="language-txt">8 truth domains are covered across multiple layers.
7 mutant scenarios catch 8 seeded bug flags because invoice/admin/API truth catches two flags in one run.
5 full Playwright tests generate 6 report rows because the threshold test writes under/exact/over reports.
</code></pre>
<p>The full gauntlet covers:</p>
<ol>
<li><p>Product page to cart truth</p>
</li>
<li><p>Coupon truth</p>
</li>
<li><p>Tax rounding truth</p>
</li>
<li><p>Free-shipping threshold truth at ₹1,999.99, ₹2,000.00, and ₹2,000.01</p>
</li>
<li><p>Locale and currency parsing across <code>en-IN</code>, <code>en-US</code>, <code>en-GB</code>, <code>de-DE</code>, and <code>fr-FR</code></p>
</li>
<li><p>Inventory race protection for a last-stock item</p>
</li>
<li><p>Email receipt truth using a local Passmark email provider</p>
</li>
<li><p>Invoice, admin, and API truth using PDF text extraction and direct API reads</p>
</li>
</ol>
<p>That is the entire project in one sentence:</p>
<blockquote>
<p>Passmark shops. Playwright records. Redis caches. Decimal math judges. ReceiptRipper refuses to ship lies.</p>
</blockquote>
<h2>Why I Built A Controlled Store</h2>
<p>For this hackathon, the obvious path was to point Passmark at an existing public app and write plain-English tests.</p>
<p>That is useful, but I wanted a stronger demo.</p>
<p>Most web testing articles stop at:</p>
<pre><code class="language-txt">The page rendered.
The button worked.
The user reached checkout.
</code></pre>
<p>ReceiptRipper asks a nastier question:</p>
<pre><code class="language-txt">Did checkout tell the same truth everywhere?
</code></pre>
<p>That meant I needed a target where every surface was visible, deterministic, and reproducible. So I built a controlled local commerce app with:</p>
<ol>
<li><p>Product grid</p>
</li>
<li><p>Cart</p>
</li>
<li><p>Checkout review page</p>
</li>
<li><p>Confirmation page</p>
</li>
<li><p>Email receipt</p>
</li>
<li><p>Admin order page</p>
</li>
<li><p>Order API</p>
</li>
<li><p>Invoice PDF</p>
</li>
<li><p>Truth report dashboard</p>
</li>
</ol>
<p>No real payment provider. No real SMTP. No Medusa. No Saleor. No fake production story.</p>
<p>Just a small store designed to expose the places where commerce systems drift.</p>
<img src="https://raw.githubusercontent.com/ladiesmans217/Breaking-Apps/main/article-assets/storefront.png" alt="ReceiptRipper storefront" style="display:block;margin:0 auto" />

<h2>The Bug That Makes The Demo Click</h2>
<p>Here is a real seeded mutant from the suite:</p>
<pre><code class="language-txt">Expected total:        ₹1,455.94
Admin order total:     ₹1,356.94
Invoice PDF total:     ₹1,455.95
Order API total:       ₹1,455.94
</code></pre>
<p>The API is right. The customer-facing confirmation is right. But admin ignored shipping and the invoice drifted by one paisa.</p>
<p>A shallow test would probably pass because checkout completed.</p>
<p>ReceiptRipper fails the run because the store told three different money stories.</p>
<img src="https://raw.githubusercontent.com/ladiesmans217/Breaking-Apps/main/article-assets/full-aggregate-report.png" alt="ReceiptRipper aggregate truth report" style="display:block;margin:0 auto" />

<p>That is why this project is not about “AI clicked buttons.”</p>
<p>It is about using AI to reach the same surfaces a user would reach, then using deterministic code to decide whether those surfaces are telling the truth.</p>
<h2>The Architecture</h2>
<p>The architecture has four layers:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody><tr>
<td>Passmark</td>
<td>Drives the human checkout journey in plain English and extracts receipt email content</td>
</tr>
<tr>
<td>Playwright</td>
<td>Captures evidence, downloads invoices, calls APIs, and coordinates the inventory race</td>
</tr>
<tr>
<td>Decimal oracle</td>
<td>Computes the expected subtotal, discount, tax, shipping, and total</td>
</tr>
<tr>
<td>Truth Report</td>
<td>Turns mismatches into JSON/HTML evidence and a <code>SHIP</code> or <code>DO NOT SHIP</code> decision</td>
</tr>
</tbody></table>
<p>The important design choice is that AI never becomes the judge of correctness.</p>
<p>Passmark owns the human-facing journey. This is the actual checkout flow:</p>
<pre><code class="language-ts">await runSteps({
  page,
  userFlow: "ReceiptRipper checkout truth flow",
  steps: [
    { description: "Click the Add button for Monsoon Hoodie" },
    { description: "Click the Add button for Ledger Mug" },
    { description: "Fill the Coupon input", data: { value: "SAVE20" } },
    { description: "Fill the Name input", data: { value: "Ada Lovelace" } },
    { description: "Fill the Email input", data: { value: "passmark-shopper@receiptripper.test" } },
    { description: "Click the Checkout button" },
    { description: "Click the Place order button" },
  ],
  assertions: [
    { assertion: "The order confirmation page is visible and shows an order total." },
  ],
  test,
  expect,
});
</code></pre>
<p>But after checkout exists, the model steps aside.</p>
<p>The oracle takes over.</p>
<h2>The Money Oracle</h2>
<p>The store uses simple INR rules:</p>
<pre><code class="language-txt">Coupon: SAVE20 gives 20% off
Tax: 18%
Shipping: ₹99.00
Free shipping threshold: ₹2,000.00
Rounding: Decimal.js, 2 decimal places
</code></pre>
<p>The expected total is computed as:</p>
<pre><code class="language-txt">subtotal = sum(line totals)
discount = subtotal * 0.20 when coupon is SAVE20
discountedSubtotal = subtotal - discount
tax = discountedSubtotal * 0.18
shipping = 0 when subtotal &gt;= 2000.00, otherwise 99.00
total = discountedSubtotal + tax + shipping
</code></pre>
<p>This is deliberately boring.</p>
<p>Money tests should be boring. Boring math is what makes the result trustworthy.</p>
<h2>The Real Checkout Flow</h2>
<p>The first version of ReceiptRipper went straight from cart to confirmation. The full version has a proper checkout draft:</p>
<pre><code class="language-txt">POST /api/checkouts
/checkout/[id]
Place order
/orders/[id]
</code></pre>
<p>That matters because checkout is its own truth surface. It can show one total while confirmation, email, invoice, API, and admin show another.</p>
<p>The app now exposes:</p>
<pre><code class="language-txt">Product page claim
Cart claim
Checkout claim
Confirmation claim
Email claim
Admin claim
API claim
Invoice claim
</code></pre>
<p>ReceiptRipper compares each claim against the oracle.</p>
<h2>The Seeded Bugs</h2>
<p>I added bug flags so every failure is reproducible.</p>
<pre><code class="language-txt">BUG_COUPON_LIES
BUG_TAX_ROUNDING_DRIFT
BUG_EMAIL_TOTAL_WRONG
BUG_INVOICE_CENT_OFF
BUG_FREE_SHIPPING_THRESHOLD_WRONG
BUG_ADMIN_IGNORES_SHIPPING
BUG_INVENTORY_DOUBLE_SELLS
BUG_LOCALE_DECIMAL_DRIFT
</code></pre>
<p>Each bug represents a real class of production problem:</p>
<table>
<thead>
<tr>
<th>Bug flag</th>
<th>What it simulates</th>
</tr>
</thead>
<tbody><tr>
<td><code>BUG_COUPON_LIES</code></td>
<td>Checkout/confirmation mishandle the discount</td>
</tr>
<tr>
<td><code>BUG_TAX_ROUNDING_DRIFT</code></td>
<td>Tax differs by ₹0.01</td>
</tr>
<tr>
<td><code>BUG_EMAIL_TOTAL_WRONG</code></td>
<td>Receipt email disagrees with checkout</td>
</tr>
<tr>
<td><code>BUG_INVOICE_CENT_OFF</code></td>
<td>PDF invoice total is off by one paisa</td>
</tr>
<tr>
<td><code>BUG_FREE_SHIPPING_THRESHOLD_WRONG</code></td>
<td>UI claims free shipping below the real threshold</td>
</tr>
<tr>
<td><code>BUG_ADMIN_IGNORES_SHIPPING</code></td>
<td>Admin order drops shipping from the total</td>
</tr>
<tr>
<td><code>BUG_INVENTORY_DOUBLE_SELLS</code></td>
<td>Two buyers can buy the only remaining item</td>
</tr>
<tr>
<td><code>BUG_LOCALE_DECIMAL_DRIFT</code></td>
<td>Locale parsing turns formatted money into the wrong number</td>
</tr>
</tbody></table>
<p>The mutant suite is green only when the bug is caught.</p>
<p>That is the core trick:</p>
<blockquote>
<p>A mutant test passes when ReceiptRipper catches the lie.</p>
</blockquote>
<h2>Passmark And Redis</h2>
<p>The hackathon gives OpenRouter credits, so I configured Passmark through OpenRouter:</p>
<pre><code class="language-ts">configure({
  ai: {
    gateway: "openrouter",
    mode: "snapshot",
    models: {
      stepExecution: "google/gemini-2.0-flash-lite-001",
      assertionPrimary: "google/gemini-2.0-flash-lite-001",
      assertionSecondary: "google/gemini-2.0-flash-lite-001",
      assertionArbiter: "google/gemini-2.0-flash-lite-001",
      utility: "google/gemini-2.0-flash-lite-001",
    },
  },
  redis: process.env.REDIS_URL ? { url: process.env.REDIS_URL } : undefined,
  email: localInboxProvider(baseURL),
});
</code></pre>
<p>Redis is important because Passmark is designed around the idea that AI discovers the flow once, then cached Playwright actions make repeat runs cheaper and faster.</p>
<p>I used Docker only for Redis:</p>
<pre><code class="language-yaml">services:
  redis:
    image: redis:8.6.3-alpine
    ports:
      - "6379:6379"
    command: ["redis-server", "--save", "", "--appendonly", "no"]
</code></pre>
<p>The Redis-backed full run passed with:</p>
<pre><code class="language-powershell">docker compose up -d redis
REDIS_URL=redis://localhost:6379 npm run test:full
docker compose down
</code></pre>
<h2>Email Without SMTP</h2>
<p>Receipt bugs often hide in email.</p>
<p>Checkout can show the right total while the customer email uses stale data, misses a discount, or rounds differently.</p>
<p>I did not want real SMTP in the critical path, so ReceiptRipper has an in-memory local inbox. When an order is created, the app writes the email receipt into local process state. A custom Passmark email provider reads from <code>/api/emails</code>.</p>
<p>That lets the suite use Passmark-style email extraction:</p>
<pre><code class="language-ts">data: {
  receiptTotal: "{{email.total:extract the Total amount:mutant-email@receiptripper.test}}"
}
</code></pre>
<p>No external mailbox. No flaky email delivery. Still a real email truth surface.</p>
<h2>Locale Truth</h2>
<p>Locale bugs are sneaky because the page still looks right to a human.</p>
<p>ReceiptRipper formats and parses INR across:</p>
<pre><code class="language-txt">en-IN
en-US
en-GB
de-DE
fr-FR
</code></pre>
<p>The <code>de-DE</code> case is especially useful because thousands and decimal separators differ from English-style formatting.</p>
<p>The locale mutant intentionally parses a localized string incorrectly. The test catches the drift before it becomes a money bug.</p>
<h2>Inventory Truth</h2>
<p>Inventory is another truth surface.</p>
<p>I added a <code>Last Stock Poster</code> product with inventory <code>1</code>.</p>
<p>The honest race test sends two buyers at the same time:</p>
<pre><code class="language-txt">Buyer A tries to buy the last item.
Buyer B tries to buy the last item.
</code></pre>
<p>The expected result:</p>
<pre><code class="language-txt">One order succeeds with 201.
One order fails with 409.
</code></pre>
<p>The mutant mode enables double-selling. In that mode both orders succeed, and the test proves ReceiptRipper can expose the inventory lie.</p>
<h2>The Truth Report</h2>
<p>Every scenario writes a JSON and HTML report:</p>
<pre><code class="language-txt">reports/&lt;scenario&gt;.json
reports/&lt;scenario&gt;.html
</code></pre>
<p>The full run also writes:</p>
<pre><code class="language-txt">reports/index.html
reports/index.json
</code></pre>
<p>The report includes:</p>
<ol>
<li><p>Scenario name</p>
</li>
<li><p>Bug flags</p>
</li>
<li><p>Expected money breakdown</p>
</li>
<li><p>Observed claims by source</p>
</li>
<li><p>Mismatches</p>
</li>
<li><p>Truth score</p>
</li>
<li><p><code>SHIP</code> or <code>DO NOT SHIP</code></p>
</li>
<li><p>Evidence paths</p>
</li>
<li><p>Invoice text</p>
</li>
<li><p>Repro command</p>
</li>
</ol>
<p>The aggregate report is intentionally simple:</p>
<pre><code class="language-txt">full-product-cart       SHIP   100%
full-threshold-under    SHIP   100%
full-threshold-exact    SHIP   100%
full-threshold-over     SHIP   100%
full-locale             SHIP   100%
full-inventory-race     SHIP   100%
</code></pre>
<p>That gives judges a quick “yes, this ran” moment before they dive into code.</p>
<h2>Final Verification</h2>
<p>I ran:</p>
<pre><code class="language-bash">npm run lint
npm run typecheck
npm run build
</code></pre>
<p>All passed.</p>
<p>Then I ran the full Redis-backed suite:</p>
<pre><code class="language-powershell">docker compose up -d redis
REDIS_URL=redis://localhost:6379 npm run test:full
docker compose down
</code></pre>
<p>Final result:</p>
<pre><code class="language-txt">Unit tests: 12 passed
Passmark-powered browser journey: 1 passed
Honest truth scenario: 1 passed
Mutant truth scenarios: 7 passed
Full truth Playwright tests: 5 passed
Full truth report rows: 6 generated
Production build: passed
Redis-backed full run: passed
</code></pre>
<p>Because the Passmark journey depends on OpenRouter, I kept the deterministic truth checks separate from the AI-gateway-dependent flow. That makes the evidence loop repeatable while still proving the sponsor tool can drive the actual checkout journey.</p>
<h2>What I Learned</h2>
<p>The obvious use case for AI regression testing is:</p>
<blockquote>
<p>Let AI click the app so I do not write selectors.</p>
</blockquote>
<p>That is useful, but ReceiptRipper made me care about a different pattern:</p>
<blockquote>
<p>Let AI reach the user-visible truth, then compare that truth against a deterministic invariant.</p>
</blockquote>
<p>For this project, the invariant is money.</p>
<p>I do not want an AI deciding whether ₹1,455.94 is correct.</p>
<p>I want the AI to behave like the shopper, reach checkout, open the email, download the invoice, and expose the claims. Then I want deterministic code to judge those claims.</p>
<p>That split feels much more reliable:</p>
<pre><code class="language-txt">AI for navigation.
Playwright for evidence.
Redis for replay.
Decimal math for truth.
</code></pre>
<h2>How To Run It</h2>
<p>Clone the repo:</p>
<pre><code class="language-bash">git clone https://github.com/ladiesmans217/Breaking-Apps.git
cd Breaking-Apps
</code></pre>
<p>Install dependencies:</p>
<pre><code class="language-bash">npm install
npx playwright install chromium
</code></pre>
<p>Create <code>.env</code>:</p>
<pre><code class="language-txt">OPENROUTER_API_KEY=sk-or-...
PASSMARK_AI=on
PASSMARK_LOG_LEVEL=info
REDIS_URL=redis://localhost:6379
</code></pre>
<p>Run the normal suite:</p>
<pre><code class="language-bash">npm test
</code></pre>
<p>Run the full original gauntlet:</p>
<pre><code class="language-bash">npm run truth:full
</code></pre>
<p>Run with Redis:</p>
<pre><code class="language-bash">docker compose up -d redis
npm run test:full
docker compose down
</code></pre>
<p>Start the app:</p>
<pre><code class="language-bash">npm run dev
</code></pre>
<p>Open:</p>
<pre><code class="language-txt">http://127.0.0.1:3100
http://127.0.0.1:3100/truth
</code></pre>
<h2>What I Would Add Next</h2>
<p>ReceiptRipper is now complete as a controlled-store truth lab. The next version should test a real commerce engine:</p>
<ol>
<li><p>Medusa or Saleor checkout as the target</p>
</li>
<li><p>Refund and return truth</p>
</li>
<li><p>Order edit truth</p>
</li>
<li><p>Payment provider webhook truth</p>
</li>
<li><p>GitHub Actions with Redis</p>
</li>
<li><p>Hosted demo with public report artifacts</p>
</li>
<li><p>Video evidence embedded directly inside reports</p>
</li>
</ol>
<p>But I would keep the core architecture unchanged.</p>
<p>The AI should not be the judge.</p>
<p>The AI should be the shopper.</p>
<h2>Final Thought</h2>
<p>Passmark made it easy to express a real user flow in plain English.</p>
<p>ReceiptRipper adds the part I think every serious AI regression suite needs:</p>
<pre><code class="language-txt">A deterministic truth oracle.
</code></pre>
<p>Because the scariest bugs are not always the ones that crash.</p>
<p>Sometimes the page loads.</p>
<p>Sometimes checkout completes.</p>
<p>Sometimes the receipt arrives.</p>
<p>And sometimes the store quietly tells three different versions of the truth.</p>
<p>ReceiptRipper catches that.</p>
<p>Built for <strong>#breakingappshackathon</strong>.</p>
<h2>Resources</h2>
<ul>
<li><p>Project repo: <a href="https://github.com/ladiesmans217/Breaking-Apps">https://github.com/ladiesmans217/Breaking-Apps</a></p>
</li>
<li><p>Passmark: <a href="https://passmark.dev/">https://passmark.dev/</a></p>
</li>
<li><p>Passmark GitHub: <a href="https://github.com/bug0inc/passmark">https://github.com/bug0inc/passmark</a></p>
</li>
<li><p>Breaking Apps Hackathon: <a href="https://hashnode.com/hackathons/breaking-things">https://hashnode.com/hackathons/breaking-things</a></p>
</li>
<li><p>Playwright docs: <a href="https://playwright.dev/docs/intro">https://playwright.dev/docs/intro</a></p>
</li>
<li><p>Next.js docs: <a href="https://nextjs.org/docs/app/getting-started/installation">https://nextjs.org/docs/app/getting-started/installation</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>