MarketCart CI › Guides
Shopify Horizon theme: a Playwright cart test that reads the real drawer
Horizon (Shopify's newer theme, version 4.4.0 at the time of writing) uses different cart markup from Dawn, so Dawn selectors find nothing and a naive test passes or fails for the wrong reason. Below: the Horizon 4.4.0 selectors, a minimal Playwright script, and the adapter MarketCart CI ships.
Horizon 4.4.0 cart markup (from the official source)
| What | Selector |
|---|---|
| Add to cart | add-to-cart-component button[name="add"] inside product-form-component |
| Selected variant | product-form-component input[name="id"] |
| Cart drawer | theme-drawer#cart-drawer > dialog (open = dialog.open); trigger [data-testid="cart-drawer-trigger"] |
| Lines | tr.cart-items__table-row:not(.cart-items__nested-line); title a.cart-items__title; options dl.cart-items__variants dd (values end with “, ”) |
| Quantity / price | input[name="updates[]"] / td.cart-items__price bdi |
| Badge / total | .cart-bubble__text-count / .cart-totals__total-value |
Minimal Playwright script
// Horizon 4.4.0 selectors (checked against Shopify/horizon source)
import { chromium } from 'playwright-core';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(process.argv[2]);
const added = page.waitForResponse((r) => /\/cart\/add(\.js)?$/.test(new URL(r.url()).pathname));
await page.click('add-to-cart-component button[name="add"], product-form-component button[name="add"]');
await added;
await page.waitForTimeout(800);
const stored = await page.evaluate(async () => (await fetch(window.Shopify.routes.root + 'cart.js')).json());
const dialog = page.locator('theme-drawer#cart-drawer dialog');
if (await dialog.count()) {
// cart_type = drawer. If "auto open" is off, open it like a shopper would; never fall back to /cart.
if (!(await dialog.evaluate((d) => d.open))) await page.click('[data-testid="cart-drawer-trigger"]');
} else {
await page.goto(new URL('cart', page.url()).href); // cart_type = page: the cart page is the real surface
}
const visible = await page.evaluate(() => {
const root = document.querySelector('theme-drawer#cart-drawer dialog[open]') ?? document.querySelector('main');
return [...root.querySelectorAll('tr.cart-items__table-row:not(.cart-items__nested-line)')].map((row) => ({
title: row.querySelector('a.cart-items__title')?.textContent?.trim(),
options: [...row.querySelectorAll('dl.cart-items__variants dd')].map((d) => d.textContent.replace(/[,\u00a0\s]+$/u, '').trim()),
qty: Number(row.querySelector('input[name="updates[]"]')?.value),
price: row.querySelector('td.cart-items__price bdi')?.textContent?.trim(),
}));
});
console.log({ stored: stored.items.map((i) => [i.variant_title, i.quantity, i.final_line_price]), visible,
badge: await page.locator('.cart-bubble__text-count').textContent(),
total: await page.locator('.cart-totals__total-value').first().textContent() });
await browser.close();
Expected and actual output
On our synthetic Horizon skins (drawer, cart page) the MarketCart Horizon adapter classified 21/21 cases as expected: healthy → PASS; wrong variant, clamped quantity, wrong amount, wrong currency sign, stored currency mismatch, stale drawer, stale badge, stale quantity → REGRESSION; required total missing and unserved market → UNSUPPORTED. Example REGRESSION reason: stored 1 line(s), visible 0 line(s) in drawer.
Tested versions and what is not verified
- Horizon 4.4.0 markup: checked against the official theme source and covered by the synthetic fixture matrix.
- Live Horizon store: not yet verified. Until a live run is recorded, treat Horizon support as fixture-verified only.
- Horizon-based sibling themes are not whitelisted; they return UNSUPPORTED until their markup is checked.
Pitfalls
- The drawer is a native
<dialog>: rows exist in the DOM even when it is closed. Checkdialog.openbefore reading. - With auto-open cart drawer disabled, open it from the header trigger; do not read
/cartinstead. - Option values carry a trailing comma and non-breaking space; strip them before comparing with
variant_title. - Theme setting
cart_typedecides the surface: drawer, or a link to/cart. - Pace your runs (15 s per store between cart cycles in MarketCart) to avoid HTTP 429.
Updated 2026-11-07. Evidence: repository evidence folder.