npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@decocms/qa

v0.9.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 11-step purchase journey (home → PLP → PDP → cart → /checkout → search) 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,mobile

The journey clicks through:

  1. visit-home — load the URL
  2. navigate-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. When the drawer's items are themselves buttons that open a submenu (signal/JS-driven menus), mark those group headers with [data-qa-menu-group] — the engine drills through them (bounded depth) until a marked link is visible. Checkbox-toggled nested groups (deco's DesktopContainer pattern) are drilled automatically. If a blocking popup/overlay intercepts the click, the engine dismisses it and retries (see Dismissing blocking overlays).
  3. enter-pdp — click the first [data-qa-product-card], extract [data-qa-pdp-title] and (optional) [data-qa-pdp-price]
  4. shipping-calc-pdp (optional) — fill [data-qa-cep-input] + click [data-qa-cep-submit]
  5. 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).
  6. open-minicart — dismiss blocking overlays (see Dismissing blocking overlays), click [data-qa-cart-icon], wait for [data-qa-minicart], then assert the cart line (see "Cart-state assertions" below)
  7. cart-persists-reload (optional) — reload the page, reopen the minicart, assert the cart line still has ≥1 [data-qa-minicart-item]
  8. shipping-calc-cart (optional) — same as step 4 but in the cart context
  9. go-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)
  10. cart-controls (optional) — click [data-qa-quantity-increment] and assert [data-qa-quantity-value] becomes 2, then click [data-qa-minicart-item-remove] and assert the cart empties
  11. search (optional, auxiliary) — if [data-qa-search-input] is visible, type a term derived from the PDP title, press Enter, and assert ≥1 [data-qa-product-card] renders in the results. A broken search fails the strict verdict but not funnelVerdict (it's not a purchase blocker).

Optional steps are skipped silently when their slugs aren't on the page.

Out-of-stock variants

When the buy flow needs a size and some sizes can't be bought, mark the OOS options with data-qa-variant-unavailable (alongside data-qa-variant-option) or give them aria-disabled="true" / disabled — the engine skips them when picking. <label for=…> options whose associated <input> is disabled are skipped too.

If nothing machine-readable marks an OOS option (style-only dimming), the engine can still recover: when picking a variant unmounts [data-qa-buy-button] (the common "swap the buy area into notify me" pattern), it dismisses the auto-opened modal, skips that size and tries the next available option — preferring options styled differently from the one that failed (OOS styling is uniform within a store, so a differently-classed option is the likely in-stock one). Bounded at 3 attempts, recorded in the step's actionDescription.

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.

Dismissing blocking overlays

Stores often show a blocking modal/popup (newsletter, cookie-consent, age-gate) before the shopper reaches a product. The engine masks automation (real UA, AutomationControlled disabled) so it sees the real shopper experience — which means the store can't detect the run to suppress the popup. So the engine dismisses overlays itself: proactively before navigating, and reactively whenever a click is intercepted — retrying a normal click once the overlay is gone (it never clicks through an overlay). In order, it tries:

  1. data-qa-dismiss (recommended) — clicks every visible [data-qa-dismiss]. Mark the popup's close control (the ✕) with this attribute and dismissal is deterministic and zero-config — no ?qa=-style store branching needed.
  2. Escape — clears overlays bound to a keydown listener.
  3. Heuristic (opt-in via features.dismissOverlaysHeuristic) — clicks a generic close affordance (a /×/fechar/close button, or [aria-label*="close"]/[aria-label*="fechar"]). Hardened to never follow a link (href) or submit a form, but best-effort and may misfire — off by default so deterministic runs aren't surprised.

Dismissal is bounded (capped rounds) and a no-op when no overlay is present, so stores without popups are unaffected.

WAF-bypass user-agent

By default the engine appends the token deco-qa-bot/1.0 to the browser user-agent on every viewport (desktop, mobile and tablet). This token matches a Cloudflare WAF skip rule scoped to the preview hosts (*.decocdn.com), so the QA bot is let through without weakening the protection for real shopper traffic.

Set QA_USER_AGENT to override the user-agent completely (the token included) — useful for one-off debugging:

QA_USER_AGENT="my-debug-agent/1.0" bunx @decocms/qa journey --url https://mystore.example.com

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. Put the marker on whatever displays the quantity — a text node (<span>1</span>), a form field (<input value="1">), a wrapper around the field (<div data-qa-quantity-value><input value="1"></div>), or carry the value on the attribute itself (data-qa-quantity-value="1"). The engine reads all of these — no mirror <span> needed for input-based quantities. If you declare the value on the attribute, bind it reactively (step 10 increments and expects it to become 2). | | 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 11-step purchase journey | | qa doctor --url <url> | Report which data-qa-* slugs are present across the funnel (home → PLP → PDP; --deep adds add-to-cart + minicart) | | qa list-slugs | Print the 31 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 31 canonical slug names (Zod-enforced).
  • features.checkoutUrlPattern — a substring the post-click checkout URL must contain (e.g. /checkout), with */** as wildcards. When set, the engine drops the [data-qa-checkout-page] assertion and validates by URL instead — works for same-origin, cross-origin (external VTEX), and hash-route SPA checkouts (…/checkout#/email). Because it's a contains match, a plain /checkout covers all of these; the legacy glob form (**/checkout**) still works. 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 over checkoutCrossOrigin.
  • features.checkoutCrossOrigin — set to true for VTEX legacy stores where the checkout opens on checkout.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. Prefer checkoutUrlPattern for 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 npm

Tests use a local HTTP server (tests/harness/server.ts) plus HTML fixtures. They run against real Chromium — no mocks for browser interactions.

License

MIT.