MarketCart CI
A release check for Shopify stores on Dawn-family themes. It adds an item in a real browser, then compares the cart Shopify stored (/cart.js) with what the shopper sees: line items, variant, quantity, line price, market currency, badge and total. The result is JSON plus an exit code, so a theme deploy fails before shoppers find the bug.
Open source CLI + GitHub ActionHosted: EUR 19 / 100 casesDawn family only Repository · llms.txt
Run the seeded demo now
Three cases against our synthetic fixture (a mock store with Dawn-style markup and planted defects), executed live by the hosted engine: a healthy cart, a stale drawer, and a live-vs-candidate pair where the candidate regressed. Results are cached for 10 minutes.
| Case | Expected | Result | Why |
|---|
Check your own store (one free case)
1. Get your verification tag and paste it into Online Store › Themes › Edit code › layout/theme.liquid, inside <head>. This proves you control the store; we only test stores that carry it.
2. Pick one product variant to test.
The password is sent over HTTPS for this one run, used in memory and never stored or logged. Inconclusive results (BLOCKED, UNSUPPORTED, ERROR) do not use up your free case. Free checks stop for the day when the shared free budget is used.
Pricing
EUR 19 = 100 completed hosted cases, valid 30 days from activation. A live-vs-candidate pair uses 2 cases. Only PASS or REGRESSION results use credits; BLOCKED, UNSUPPORTED and ERROR are released. No subscription. The CLI and Action run locally for free.
Sold through Payhip (seller: NorthGrid Digital). Your license key arrives automatically after payment and is checked on every job; refunds disable it.
Benchmark: 20 cases, expected vs actual
Run bench-20261107T141346Z, engine 0.1.0+49668598: 20/20 classified as expected, planted regressions caught 10/10, false PASS 0. The expected statuses were frozen before the engine was written. These are synthetic cases (one planted defect per scenario) by the same author as the engine: they show which bug classes are detected, not how a particular live store behaves. Methodology · raw JSON
| Case | Group | Planted | Expected | Actual | Engine reason |
|---|---|---|---|---|---|
| C01 | healthy-baseline | Single default variant, qty 1, EUR market verified; stored == visible drawer/badge/total. | PASS | PASS | stored == visible across 1 line(s), market IE/EUR verified in-session |
| C02 | healthy-baseline | Non-default variant selected via ?variant; stored and visible both Blue / M. | PASS | PASS | stored == visible across 1 line(s), market IE/EUR verified in-session |
| C03 | healthy-baseline | Quantity 3; stored qty, visible qty, badge 3 and line total agree. | PASS | PASS | stored == visible across 1 line(s), market IE/EUR verified in-session |
| C04 | healthy-baseline | Zero-decimal currency: JPY minor units are whole yen; ¥7,800 must equal stored 7800 without a /100 conversion. | PASS | PASS | stored == visible across 1 line(s), market JP/JPY verified in-session |
| C05 | stale-visible-state | Item stored in cart but the drawer keeps its stale empty render. | REGRESSION | REGRESSION | stored 1 line(s), visible 0 line(s) in drawer; stored line Linen Shirt (Red / M, variant 4101) not visible |
| C06 | stale-visible-state | Drawer correct, header cart badge not updated (adapter declares badge; fixture expects it). | REGRESSION | REGRESSION | stored item_count 1, badge absent |
| C07 | stale-visible-state | Stored qty 2, drawer shows stale qty 1. | REGRESSION | REGRESSION | variant 4101: stored qty 2, visible qty 1 |
| C08 | item-quantity-drift | Shopper selected Blue / M (4102); theme JS posts the default variant 4101. | REGRESSION | REGRESSION | requested variant 4102 not in stored cart (stored: 4101); unexpected stored variant 4101 |
| C09 | item-quantity-drift | Two items stored; drawer re-render drops the second line. | REGRESSION | REGRESSION | stored 2 line(s), visible 1 line(s) in drawer; stored line Merino Socks (Default Title, variant 6301) not visible |
| C10 | item-quantity-drift | Server persists qty 2 (clamp) while the optimistic UI shows the requested 3. | REGRESSION | REGRESSION | variant 4101: requested qty 3, stored qty 2; variant 4101: stored qty 2, visible qty 3; variant 4101: stored line 4800 EUR minor, visible 7200 |
| C11 | currency-price | Market IE/EUR verified in-session, but the stored cart currency is USD (wrong currency). | REGRESSION | REGRESSION | stored cart currency USD, verified market currency EUR; variant 4101: visible "$26.00" indicates USD|CAD|AUD|NZD|MXN|SGD|HKD|CLP|COP|ARS, market EUR; visible total "$26.00" indicates USD|CAD|AUD|NZD|MXN|SGD|HKD|CLP|COP|A |
| C12 | currency-price | Stored EUR 24.00; drawer shows the same number with the wrong currency ($24.00). | REGRESSION | REGRESSION | variant 4101: visible "$24.00" indicates USD|CAD|AUD|NZD|MXN|SGD|HKD|CLP|COP|ARS, market EUR; visible total "$24.00" indicates USD|CAD|AUD|NZD|MXN|SGD|HKD|CLP|COP|ARS, market EUR |
| C13 | currency-price | Stored EUR 24.00; drawer shows a wrong amount (€20.00). | REGRESSION | REGRESSION | variant 4101: stored line 2400 EUR minor, visible 2000 |
| C14 | currency-price | Equivalent localized formatting: de-DE '1.234,56 €' equals stored 123456 EUR minor units (dot = grouping, comma = decimal). | PASS | PASS | stored == visible across 1 line(s), market DE/EUR verified in-session |
| C15 | live-candidate | Healthy live vs unchanged healthy candidate: both pass, observations identical. Consumes 2 cases. | PASS | PASS | live and candidate both pass with identical stored/visible observations |
| C16 | live-candidate | Healthy live vs broken candidate (stale drawer). Consumes 2 cases. | REGRESSION | REGRESSION | candidate regressed (live PASS) |
| C17 | inconclusive | Storefront answers 429 with a bot-challenge interstitial; never PASS. | BLOCKED | BLOCKED | storefront blocked automation (HTTP 429, challenge page) |
| C18 | inconclusive | Non-Dawn theme markup: no supported adapter; never PASS. | UNSUPPORTED | UNSUPPORTED | no supported adapter: theme schema "Debut" |
| C19 | inconclusive | Add-to-cart request hangs; per-case timeout fires (6 s fixture timeout, one bounded retry); never PASS. | ERROR | ERROR | case timeout after 6000 ms |
| C20 | inconclusive | Requested JP/JPY but the store keeps serving IE/EUR in-session; the market cannot be verified, even though the cart itself is internally consistent; never PASS. | UNSUPPORTED | UNSUPPORTED | requested market JP/JPY not verified in-session (observed IE/EUR) |
How it compares (scoped)
On the C05 stale-drawer and C08 wrong-variant synthetic fixtures, E-Com Checkout Auditor returned success where our parity contract expects REGRESSION. That is two synthetic cases, not a general superiority claim. PromiseCheck is a differently scoped Shopify Admin app, listed as a price anchor and not tested.
| Product | What it checks | Result on our fixture | Price | Evidence |
|---|---|---|---|---|
| MarketCart CI (this project) | Real browser; theme add-to-cart; /cart.js read independently of the DOM; drawer/cart page, badge, total, in-session market currency | C05 stale drawer: REGRESSION · C08 wrong variant: REGRESSION (20/20 synthetic cases as expected) | EUR 19 / 100 completed hosted cases (30 days); CLI free locally | EXECUTED (synthetic fixture + Founder-owned live Dawn dev store) · source |
| E-Com Checkout Auditor (Apify Actor) | Browser journey product → cart → checkout page, pass/fail per phase | C05 stale drawer: returned success (run nv2AeksMAjnh2ksTq) · C08 wrong variant: returned success (run bJKqMgPGZy7PxTBJq). Our parity contract expects REGRESSION for both. n = 2 synthetic cases | USD 0.05 per result (Apify Store, 2026-11-06) | EXECUTED n=2 on our synthetic fixture · source |
| Shopify Variant Cartability Checker (Apify Actor) | Ajax cart API transaction path (/products/x.js, /cart/add.js); never reads the rendered drawer by design | Inconclusive: our path-prefixed fixture is incompatible with root-relative tools (runs bs9d3gdsxiMNjjmig, fv8KT7HOpo32sX8cB) | USD 0.005 per result (2026-11-06) | EXECUTED, inconclusive (fixture limitation) · source |
| PromiseCheck: Cart Monitoring (Shopify app) | Add to Cart, options and cart quantities on desktop/mobile; draft-theme vs saved live scan; hourly checks; screenshots, alerts | Not tested (differently scoped Shopify Admin app; price anchor only) | USD 79.99 / month, 14-day trial (listing read 2026-11-07) | DOCUMENTED, UNTESTED · source |
| StoreCheck Markets (Shopify app) | Live cart checks per market: availability, currency, totals, quantities, discounts | Not tested | Free manual / USD 9.99 per month automated (listing read 2026-11-06) | DOCUMENTED, UNTESTED · source |
FAQ
Why does the Dawn cart drawer show an empty or stale cart while /cart.js already has the item?
Dawn's product form posts to /cart/add.js and asks Shopify to return the re-rendered cart drawer and cart icon sections in the same response; the drawer then swaps in that HTML. If a customization, an app script or a missing section breaks that swap, Shopify has stored the item (it is in /cart.js) but the shopper still sees the old drawer and badge. MarketCart CI reads /cart.js independently of the page and compares it with what the drawer shows; any difference is a REGRESSION with the exact mismatch.
How do I catch a Shopify Markets currency mismatch in the cart?
Check three things in the same browser session: the market Shopify actually served (window.Shopify.country and window.Shopify.currency.active), the stored cart currency in /cart.js, and the currency shown next to every visible price and the total. They must agree, with no conversion applied. MarketCart CI does exactly that; if the session never gets the requested market, the result is UNSUPPORTED rather than a PASS you cannot trust.
How can I run cart regression tests in CI before publishing a Shopify theme?
Run the open-source MarketCart CI CLI or GitHub Action on every theme change. Describe cases as JSON (product URL, variant, quantity, market), optionally as a pair: the published theme versus an unpublished theme preview. The job exits 0 on PASS, 1 on REGRESSION and 2 when a case is inconclusive, so a failing cart blocks the deploy. Local runs are free; hosted runs use prepaid case credits.
Limits, stated plainly
- Dawn and Dawn-family themes only. Horizon (Shopify's newer default) and other themes return UNSUPPORTED, never PASS.
- Never opens checkout, never submits payment, never logs in, no anti-bot evasion. Bot challenges and HTTP 429 return BLOCKED.
- The market is verified inside the session; no currency conversion is ever applied. Zero-decimal currencies rely on Shopify's ×100 Ajax convention, verified on synthetic data only.
- Shopify rate-limits rapid cart traffic; large suites should be spaced out. Concurrency 1 in hosted mode.