@decocms/qa
v0.3.0
Published
E2E QA test runner for deco.cx ecommerce stores. Runs a deterministic purchase journey against data-qa-marked DOM and emits JUnit XML + JSON report.
Readme
@decocms/qa
E2E QA test runner for deco.cx ecommerce stores. Runs a deterministic 10-step purchase journey (home → PLP → PDP → cart → /checkout) against a single URL and emits JUnit XML + a JSON report + screenshots. The journey asserts cart state — quantity, variant, price, and that the cart isn't empty — so a broken add-to-cart fails the run instead of passing green.
Selectors are resolved from data-qa-* attributes on the store's JSX, with a configurable override fallback in .qarc.json. No LLM discovery, no visual diff — deterministic by design. The marking is consumed by the setup-deco-ecommerce-qa Claude Code skill that scaffolds GitHub Actions workflows in store repos.
Quickstart
bunx @decocms/qa journey \
--url https://mystore.example.com \
--junit junit.xml \
--github \
--viewports desktop,mobileThe journey clicks through:
visit-home— load the URLnavigate-plp— click the first visible[data-qa-category-link]. If no visible match exists and[data-qa-menu-trigger]is present, the engine clicks the trigger (e.g. a hamburger button) to open the menu drawer, then clicks the category link.enter-pdp— click the first[data-qa-product-card], extract[data-qa-pdp-title]and (optional)[data-qa-pdp-price]shipping-calc-pdp(optional) — fill[data-qa-cep-input]+ click[data-qa-cep-submit]add-to-cart— click[data-qa-buy-button]. If the click opens a size/variant modal (common on mobile), the engine picks the first in-stock[data-qa-variant-option], clicks[data-qa-variant-confirm]if present, then verifies via[data-qa-cart-count](soft — never fails the step).open-minicart— dismiss sticky overlays (Escape), click[data-qa-cart-icon], wait for[data-qa-minicart], then assert the cart line (see "Cart-state assertions" below)cart-persists-reload(optional) — reload the page, reopen the minicart, assert the cart line still has ≥1[data-qa-minicart-item]shipping-calc-cart(optional) — same as step 4 but in the cart contextgo-checkout— click[data-qa-minicart-checkout], assert[data-qa-checkout-page], verify the PDP title persisted in the cart DOM (preferring[data-qa-minicart-item-name]when present)cart-controls(optional) — click[data-qa-quantity-increment]and assert[data-qa-quantity-value]becomes2, then click[data-qa-minicart-item-remove]and assert the cart empties
Optional steps are skipped silently when their slugs aren't on the page.
After every navigation the engine waits for the network to settle and scrolls the page to trigger IntersectionObserver-driven lazy content (product grids), and polls for each required selector for up to 10s — so lazily-hydrated storefronts don't flake.
The variant slugs (data-qa-variant-option, data-qa-variant-confirm) and the badge slug (data-qa-cart-count) are optional: add them only on stores where buying requires picking a size or where you want add-to-cart verified by the cart badge.
Cart-state assertions
Once the minicart is open (step 6), the engine validates the cart line — but only for the markers a store has actually added. A missing marker means "not verified" (skipped), never a failure, so stores adopt the checks incrementally:
| Marker | Assertion |
|---|---|
| data-qa-minicart-items | The cart-line list wrapper (rendered even when empty). When present and it contains zero data-qa-minicart-item rows → fail (empty cart — add-to-cart didn't persist). Also scopes the line queries below, so a recommendation carousel can't be mistaken for a cart line. |
| data-qa-minicart-item | One element per cart line. |
| data-qa-quantity-value | Must read 1 after a single add. |
| data-qa-minicart-item-variant | Must contain the size picked in step 5 (exact token match). |
| data-qa-minicart-item-price + data-qa-pdp-price | Line price must be > 0 and ≥ the PDP "from" price. |
| data-qa-minicart-item-name | Used at go-checkout to confirm the right product reached the checkout; a mismatch here (and only here) escalates step 9 to a failure. |
Additional optional markers: data-qa-minicart-subtotal, data-qa-minicart-total, data-qa-quantity-increment, data-qa-quantity-decrement, data-qa-minicart-item-remove (the last three drive the cart-controls step).
Commands
| Command | Purpose |
|---|---|
| qa journey --url <url> | Run the 10-step purchase journey |
| qa doctor --url <url> | Report which data-qa-* slugs are present on a URL |
| qa list-slugs | Print the 27 canonical slugs as JSON |
qa journey flags
| Flag | Type | Purpose |
|---|---|---|
| --url <url> | string | Target URL (required) |
| --junit <file> | string | Emit JUnit XML to this path |
| --github | boolean | Emit GitHub Actions ::group:: / ::error:: annotations |
| --viewports <list> | string | Comma-separated: desktop,mobile,tablet. Default: desktop. |
| --cep <cep> | string | Override the CEP used in shipping-calc steps |
| --smoke | boolean | Run only steps 1,2,3,5 (skips shipping, minicart, checkout) |
| --headed | boolean | Run with a visible browser (local debug) |
| --debug | boolean | Pause for inspection (implies --headed) |
.qarc.json config
Lives at the root of the store repo. All fields except url are optional.
{
"url": "https://farmrio.com.br",
"cep": "01310-100",
"viewports": ["desktop", "mobile"],
"selectors": {
"data-qa-buy-button": "button.custom-add-to-cart"
},
"features": {
"checkoutUrlPattern": "**/checkout**"
}
}selectors— per-slug CSS override used when[data-qa-<slug>]is missing. Keys are restricted to the 27 canonical slug names (Zod-enforced).features.checkoutUrlPattern— a Playwright URL glob (e.g.**/checkout**) that the post-click checkout URL must match. When set, the engine drops the[data-qa-checkout-page]assertion and validates by URL match instead — works for both same-origin and cross-origin (external VTEX) checkouts. On a local base (localhost/127.0.0.1) where the click never reaches a matching URL, the step is skipped (verdict stays pass) rather than failing. Takes precedence overcheckoutCrossOrigin.features.checkoutCrossOrigin— set totruefor VTEX legacy stores where the checkout opens oncheckout.vtex.com.br. The engine drops the[data-qa-checkout-page]assertion and validates by URL change instead; the PDP-title persistence sweep still runs on the VTEX DOM. PrefercheckoutUrlPatternfor new configs.
CLI flags take precedence over .qarc.json.
Output
qa-output/<runId>/
├── report.json # full structured report (validated against the v0.1 Zod schema)
└── screenshots/
├── 1-visit-home.png
├── 2-navigate-plp.png
└── ...When --junit <file> is passed, a separate JUnit XML file is written for dorny/test-reporter@v2 or similar CI parsers.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Journey passed (all required steps OK; optional steps OK or deliberately skipped) |
| 1 | Assertion failure (a required step failed — the standard red-test case) |
| 2 | Setup failure (URL invalid, .qarc.json broken, browser couldn't launch) |
| 3 | Global timeout (journey exceeded its deadline; pages were force-closed) |
Development
bun install
bunx playwright install chromium
bun run check # tsc --noEmit
bun run lint # biome lint
bun run test # vitest run
bun run build # bundle dist/cli.js for npmTests use a local HTTP server (tests/harness/server.ts) plus HTML fixtures. They run against real Chromium — no mocks for browser interactions.
License
MIT.
