@decocms/parity 0.17.1 → 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.3](https://github.com/decocms/parity/compare/v0.17.2...v0.17.3) (2026-07-28)
11
+
12
+ ### Fixed
13
+
14
+ * **`open-minicart` fallback click when already-open drawer is empty (issue #159).** Sites that use a react-query on-demand cart (query `enabled` only when a cart-intent signal is `true`) never hydrate the drawer when it opens via an add-to-cart toast side-effect — the intent signal is only set on an explicit cart-icon click, so the panel renders empty and `validateCartContainsTitle` finds 0 items. When `cartOpenMethod === "already-open"` and validation returns `found=false`, parity now looks up the `minicartTrigger` and clicks it explicitly (mirroring what the user does), waits for cart hydration, then re-validates. The fallback is skipped entirely when the drawer hydrates normally via the initial validation.
15
+
10
16
  ## [0.17.0](https://github.com/decocms/parity/compare/v0.16.0...v0.17.0) (2026-07-28)
11
17
 
12
18
  ### Added
package/dist/cli.js CHANGED
@@ -51258,6 +51258,16 @@ async function validateCartContainsTitle(page, expectedTitle, ctx) {
51258
51258
  await page.waitForTimeout(2000);
51259
51259
  observed = await sweepTitles();
51260
51260
  }
51261
+ if (observed.length === 0) {
51262
+ const panelSelectors = selFor(ctx, "minicartPanel");
51263
+ if (panelSelectors.length > 0) {
51264
+ dlog2(ctx, " validateCartContainsTitle: panel empty — waiting up to 5s for hydration");
51265
+ await Promise.race(panelSelectors.map((p) => page.waitForSelector(`${p} *`, { timeout: 5000 }).catch(() => {
51266
+ return;
51267
+ })));
51268
+ observed = await sweepTitles();
51269
+ }
51270
+ }
51261
51271
  dlog2(ctx, ` validateCartContainsTitle: observed ${observed.length} titles`);
51262
51272
  if (observed.length === 0) {
51263
51273
  return { found: false, observedTitles: [], method: "none" };
@@ -52036,49 +52046,62 @@ async function flowPurchaseJourney(ctx) {
52036
52046
  const beforeUrl7 = page.url();
52037
52047
  const spBefore7 = screenshotPath(ctx, "pj-7-minicart-before");
52038
52048
  await screenshotStable(page, { path: spBefore7, fullPage: false });
52039
- let miniHit = await firstVisibleLocator(page, selFor(ctx, "minicartTrigger"));
52040
- let miniRecovered = false;
52041
- if (!miniHit && budget.remaining > 0) {
52042
- const recovery = await attemptRecovery(page, ctx, "open-minicart", "Abrir o minicart/drawer do carrinho", selFor(ctx, "minicartTrigger"));
52043
- if (recovery) {
52044
- miniHit = recovery;
52045
- miniRecovered = true;
52046
- budget.remaining--;
52047
- }
52048
- }
52049
52049
  let cartOpenMethod = "failed";
52050
52050
  let miniText = "";
52051
52051
  let cartRevealMode = "unknown";
52052
52052
  let revealDiagnostics;
52053
52053
  const drawerAlreadyOpen = await isCartRevealed(page, expectedProductTitle, ctx) !== null;
52054
- if (miniHit) {
52055
- miniText = await miniHit.locator.innerText().catch(() => "");
52056
- cartRevealMode = await detectCartRevealMode(page, miniHit.locator, drawerAlreadyOpen, ctx.viewport).catch(() => "unknown");
52057
- dlog2(ctx, `step 7 open-minicart: cartRevealMode=${cartRevealMode}`);
52058
- let openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52059
- cartOpenMethod = openResult.method;
52060
- revealDiagnostics = openResult.diagnostics;
52061
- if (cartOpenMethod === "failed" && revealDiagnostics && budget.remaining > 0) {
52062
- const recovery = await attemptRecovery(page, ctx, "open-minicart", "Abrir o minicart/drawer do carrinho — o trigger foi clicado mas o drawer não revelou", [miniHit.selector], summarizeCartRevealDiagnostics(revealDiagnostics));
52054
+ let miniHit = null;
52055
+ let miniRecovered = false;
52056
+ if (drawerAlreadyOpen) {
52057
+ cartOpenMethod = "already-open";
52058
+ cartRevealMode = "inline-notification";
52059
+ dlog2(ctx, "step 7 open-minicart: drawer already visible — skipping trigger lookup");
52060
+ } else {
52061
+ miniHit = await firstVisibleLocator(page, selFor(ctx, "minicartTrigger"));
52062
+ if (!miniHit && budget.remaining > 0) {
52063
+ const recovery = await attemptRecovery(page, ctx, "open-minicart", "Abrir o minicart/drawer do carrinho", selFor(ctx, "minicartTrigger"));
52063
52064
  if (recovery) {
52064
- budget.remaining--;
52065
52065
  miniHit = recovery;
52066
52066
  miniRecovered = true;
52067
- openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52068
- cartOpenMethod = openResult.method;
52069
- revealDiagnostics = openResult.diagnostics;
52067
+ budget.remaining--;
52070
52068
  }
52071
52069
  }
52072
- } else {
52073
- if (drawerAlreadyOpen) {
52074
- cartOpenMethod = "already-open";
52075
- cartRevealMode = "inline-notification";
52076
- dlog2(ctx, "step 7 open-minicart: no trigger, drawer already visible");
52070
+ if (miniHit) {
52071
+ miniText = await miniHit.locator.innerText().catch(() => "");
52072
+ cartRevealMode = await detectCartRevealMode(page, miniHit.locator, false, ctx.viewport).catch(() => "unknown");
52073
+ dlog2(ctx, `step 7 open-minicart: cartRevealMode=${cartRevealMode}`);
52074
+ let openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52075
+ cartOpenMethod = openResult.method;
52076
+ revealDiagnostics = openResult.diagnostics;
52077
+ if (cartOpenMethod === "failed" && revealDiagnostics && budget.remaining > 0) {
52078
+ const recovery = await attemptRecovery(page, ctx, "open-minicart", "Abrir o minicart/drawer do carrinho — o trigger foi clicado mas o drawer não revelou", [miniHit.selector], summarizeCartRevealDiagnostics(revealDiagnostics));
52079
+ if (recovery) {
52080
+ budget.remaining--;
52081
+ miniHit = recovery;
52082
+ miniRecovered = true;
52083
+ openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52084
+ cartOpenMethod = openResult.method;
52085
+ revealDiagnostics = openResult.diagnostics;
52086
+ }
52087
+ }
52077
52088
  }
52078
52089
  }
52079
52090
  let step7Validation;
52080
52091
  if (expectedProductTitle && cartOpenMethod !== "failed") {
52081
- const v = await validateCartContainsTitle(page, expectedProductTitle, ctx);
52092
+ let v = await validateCartContainsTitle(page, expectedProductTitle, ctx);
52093
+ if (cartOpenMethod === "already-open" && !v.found) {
52094
+ const triggerHit = await firstVisibleLocator(page, selFor(ctx, "minicartTrigger"));
52095
+ if (triggerHit) {
52096
+ dlog2(ctx, "step 7: already-open drawer empty — clicking minicartTrigger to activate on-demand query");
52097
+ miniHit = triggerHit;
52098
+ await triggerHit.locator.click({ timeout: 3000 }).catch(() => {
52099
+ return;
52100
+ });
52101
+ await waitForCartHydration(page);
52102
+ v = await validateCartContainsTitle(page, expectedProductTitle, ctx);
52103
+ }
52104
+ }
52082
52105
  let reasonText;
52083
52106
  if (!v.found) {
52084
52107
  if (v.observedTitles.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/parity",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "E2E parity validator for site migrations. Compares prod vs cand and reports UI, functional, SEO, visual, and Web Vitals deltas with an LLM-ranked HTML report.",
5
5
  "type": "module",
6
6
  "license": "MIT",