@decocms/parity 0.15.0 → 0.16.0
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 +10 -0
- package/dist/cli.js +51 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.16.0](https://github.com/decocms/parity/compare/v0.15.1...v0.16.0) (2026-07-28)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
* **Configurable `minicartPanel` selector for open-minicart reveal detection (issue #149).** `isCartUiVisible` and the title-scope sweep in `validateCartContainsTitleQuick` used a hardcoded list of name-based patterns (`[role='dialog']`, `[class*='minicart']`, etc.) to detect that the cart drawer was open. Any site built with utility-CSS (Tailwind) and `data-qa-*` testing attributes — a common modern stack — has no class/attribute those patterns can match, so detection always returns `null` and the step reports "failed" even when the drawer is genuinely open. The new `minicartPanel` selector key (`.parityrc.json`) is tried first, ahead of all hardcoded patterns. Built-in defaults cover `[data-qa-minicart]`, `[data-minicart]`, `[data-minicart-drawer]`, `[data-testid='minicart']` and common class patterns — so the `data-qa-*` case is handled out-of-the-box. Override with a single selector that matches the drawer root for your specific site.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* **`dismissOverlays` stall in `openMinicart` (issue #151).** The named-selector sweep inside `dismissOverlays` used a 400ms per-selector cap; with 12+ selectors none of which match, that's ~5s minimum before `openMinicart` could proceed — and on heavy pages the CDP calls could stall much longer, causing 50-100s gaps with zero debug output. Fixed by: (1) tightening the per-selector probe to 80ms (fast-failing `count()=0` before any `isVisible` call), (2) adding `dlog` at the entry of `dismissOverlays` and `openMinicart` so slow sweeps show up in `DEBUG_PARITY=1` output, and (3) wrapping the `dismissOverlays` call inside `openMinicart` with a 4s hard cap so it degrades gracefully instead of silently consuming the step budget.
|
|
19
|
+
|
|
10
20
|
## [0.15.0](https://github.com/decocms/parity/compare/v0.14.0...v0.15.0) (2026-07-27)
|
|
11
21
|
|
|
12
22
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -41389,6 +41389,7 @@ var ParityRc = z.object({
|
|
|
41389
41389
|
quantityInput: z.string().optional(),
|
|
41390
41390
|
minicartCount: z.string().optional(),
|
|
41391
41391
|
cartOpenedIndicator: z.string().optional(),
|
|
41392
|
+
minicartPanel: z.string().optional(),
|
|
41392
41393
|
searchTrigger: z.string().optional(),
|
|
41393
41394
|
searchInput: z.string().optional(),
|
|
41394
41395
|
searchSuggestions: z.string().optional(),
|
|
@@ -49545,6 +49546,7 @@ var SelectorKey = z2.enum([
|
|
|
49545
49546
|
"quantityInput",
|
|
49546
49547
|
"minicartCount",
|
|
49547
49548
|
"cartOpenedIndicator",
|
|
49549
|
+
"minicartPanel",
|
|
49548
49550
|
"searchTrigger",
|
|
49549
49551
|
"searchInput",
|
|
49550
49552
|
"searchSuggestions",
|
|
@@ -49949,6 +49951,17 @@ var DEFAULT_SELECTORS = {
|
|
|
49949
49951
|
"[role='dialog']:has-text('adicionado')",
|
|
49950
49952
|
"[role='dialog']:has-text('added')"
|
|
49951
49953
|
],
|
|
49954
|
+
minicartPanel: [
|
|
49955
|
+
"[data-qa-minicart]",
|
|
49956
|
+
"[data-minicart]",
|
|
49957
|
+
"[data-minicart-drawer]",
|
|
49958
|
+
"[data-drawer='minicart']",
|
|
49959
|
+
"[data-testid='minicart']",
|
|
49960
|
+
"[data-testid='cart-drawer']",
|
|
49961
|
+
"[class*='minicart' i]",
|
|
49962
|
+
"[class*='cart-drawer' i]",
|
|
49963
|
+
"[class*='drawer-cart' i]"
|
|
49964
|
+
],
|
|
49952
49965
|
cartItemRow: [
|
|
49953
49966
|
"[data-cart-item]",
|
|
49954
49967
|
"[data-testid='cart-item']",
|
|
@@ -50706,15 +50719,21 @@ async function dismissBlockingOverlay(page, ctx, target) {
|
|
|
50706
50719
|
function describe(b) {
|
|
50707
50720
|
return `${b.tag}${b.id ? `#${b.id}` : ""}${b.className ? `.${b.className.split(/\s+/)[0]}` : ""}`;
|
|
50708
50721
|
}
|
|
50722
|
+
var OVERLAY_PROBE_CAP_MS = 80;
|
|
50709
50723
|
async function dismissOverlays(page, ctx) {
|
|
50724
|
+
const sels = overlaySelectorsFor(ctx.rc);
|
|
50725
|
+
dlog2(ctx, ` dismissOverlays: checking ${sels.length} selector(s)…`);
|
|
50710
50726
|
const dismissals = [];
|
|
50711
|
-
for (const sel of
|
|
50727
|
+
for (const sel of sels) {
|
|
50712
50728
|
try {
|
|
50713
50729
|
const overlay = page.locator(sel).first();
|
|
50714
|
-
|
|
50730
|
+
const cnt = await withCap(overlay.count(), OVERLAY_PROBE_CAP_MS, 0);
|
|
50731
|
+
if (cnt === 0)
|
|
50732
|
+
continue;
|
|
50733
|
+
if (!await withCap(overlay.isVisible({ timeout: OVERLAY_PROBE_CAP_MS }).catch(() => false), OVERLAY_PROBE_CAP_MS, false))
|
|
50715
50734
|
continue;
|
|
50716
50735
|
const closer = overlay.locator(CLOSE_BUTTON_SELECTOR).first();
|
|
50717
|
-
if (await withCap(closer.isVisible({ timeout:
|
|
50736
|
+
if (await withCap(closer.isVisible({ timeout: OVERLAY_PROBE_CAP_MS }).catch(() => false), OVERLAY_PROBE_CAP_MS, false)) {
|
|
50718
50737
|
await closer.click({ timeout: 1500 }).catch(() => {
|
|
50719
50738
|
return;
|
|
50720
50739
|
});
|
|
@@ -50750,8 +50769,10 @@ async function readCartCount(page, ctx) {
|
|
|
50750
50769
|
}
|
|
50751
50770
|
return 0;
|
|
50752
50771
|
}
|
|
50753
|
-
async function isCartUiVisible(page) {
|
|
50772
|
+
async function isCartUiVisible(page, ctx) {
|
|
50773
|
+
const configured = ctx ? [...selFor(ctx, "minicartPanel"), ...selFor(ctx, "cartOpenedIndicator")] : [];
|
|
50754
50774
|
return firstVisible(page, [
|
|
50775
|
+
...configured,
|
|
50755
50776
|
"[role='dialog']:visible",
|
|
50756
50777
|
"[aria-modal='true']:visible",
|
|
50757
50778
|
"[data-minicart][aria-hidden='false']",
|
|
@@ -50837,16 +50858,17 @@ async function detectCartRevealMode(page, trigger, drawerAlreadyOpen, viewport)
|
|
|
50837
50858
|
return "click-drawer";
|
|
50838
50859
|
return "unknown";
|
|
50839
50860
|
}
|
|
50840
|
-
async function isCartRevealed(page, expectedProductTitle) {
|
|
50861
|
+
async function isCartRevealed(page, expectedProductTitle, ctx) {
|
|
50841
50862
|
if (expectedProductTitle) {
|
|
50842
|
-
const v = await validateCartContainsTitleQuick(page, expectedProductTitle);
|
|
50863
|
+
const v = await validateCartContainsTitleQuick(page, expectedProductTitle, ctx);
|
|
50843
50864
|
if (v)
|
|
50844
50865
|
return `title-found:${v}`;
|
|
50845
50866
|
}
|
|
50846
|
-
return isCartUiVisible(page);
|
|
50867
|
+
return isCartUiVisible(page, ctx);
|
|
50847
50868
|
}
|
|
50848
|
-
async function validateCartContainsTitleQuick(page, expectedTitle) {
|
|
50869
|
+
async function validateCartContainsTitleQuick(page, expectedTitle, ctx) {
|
|
50849
50870
|
const quickSelectors = [
|
|
50871
|
+
...ctx ? selFor(ctx, "minicartPanel") : [],
|
|
50850
50872
|
"[role='dialog']",
|
|
50851
50873
|
"[class*='minicart' i]",
|
|
50852
50874
|
"[class*='cart' i]",
|
|
@@ -50881,9 +50903,14 @@ async function waitForCartHydration(page) {
|
|
|
50881
50903
|
}
|
|
50882
50904
|
async function openMinicart(page, trigger, ctx, expectedProductTitle) {
|
|
50883
50905
|
const beforeUrl = page.url();
|
|
50884
|
-
|
|
50906
|
+
dlog2(ctx, ` openMinicart: starting — trigger=${trigger.selector} title=${expectedProductTitle?.slice(0, 40) ?? "none"}`);
|
|
50907
|
+
await Promise.race([
|
|
50908
|
+
dismissOverlays(page, ctx),
|
|
50909
|
+
new Promise((resolve) => setTimeout(resolve, 4000))
|
|
50910
|
+
]);
|
|
50911
|
+
dlog2(ctx, " openMinicart: dismissOverlays done — checking alreadyOpen…");
|
|
50885
50912
|
await page.waitForTimeout(800);
|
|
50886
|
-
const alreadyOpen = await isCartRevealed(page, expectedProductTitle);
|
|
50913
|
+
const alreadyOpen = await isCartRevealed(page, expectedProductTitle, ctx);
|
|
50887
50914
|
if (alreadyOpen) {
|
|
50888
50915
|
dlog2(ctx, ` openMinicart: already-open (matched ${alreadyOpen})`);
|
|
50889
50916
|
return { method: "already-open", url: beforeUrl, visibleMarker: alreadyOpen };
|
|
@@ -50896,7 +50923,7 @@ async function openMinicart(page, trigger, ctx, expectedProductTitle) {
|
|
|
50896
50923
|
return;
|
|
50897
50924
|
});
|
|
50898
50925
|
await page.waitForTimeout(1500);
|
|
50899
|
-
const hoverOpened = await isCartRevealed(page, expectedProductTitle);
|
|
50926
|
+
const hoverOpened = await isCartRevealed(page, expectedProductTitle, ctx);
|
|
50900
50927
|
if (hoverOpened) {
|
|
50901
50928
|
dlog2(ctx, ` openMinicart: hover opened drawer (${hoverOpened})`);
|
|
50902
50929
|
return { method: "hover", url: page.url(), visibleMarker: hoverOpened };
|
|
@@ -50921,7 +50948,7 @@ async function openMinicart(page, trigger, ctx, expectedProductTitle) {
|
|
|
50921
50948
|
return { method: "click-navigate", url: page.url(), visibleMarker: null };
|
|
50922
50949
|
}
|
|
50923
50950
|
await page.waitForTimeout(800);
|
|
50924
|
-
const tapOpened = await isCartRevealed(page, expectedProductTitle);
|
|
50951
|
+
const tapOpened = await isCartRevealed(page, expectedProductTitle, ctx);
|
|
50925
50952
|
if (tapOpened) {
|
|
50926
50953
|
dlog2(ctx, ` openMinicart: tap opened drawer (${tapOpened})`);
|
|
50927
50954
|
return { method: "click", url: page.url(), visibleMarker: tapOpened };
|
|
@@ -50946,7 +50973,7 @@ async function openMinicart(page, trigger, ctx, expectedProductTitle) {
|
|
|
50946
50973
|
return { method: "click-navigate", url: page.url(), visibleMarker: null };
|
|
50947
50974
|
}
|
|
50948
50975
|
await page.waitForTimeout(1500);
|
|
50949
|
-
const clickOpened = await isCartRevealed(page, expectedProductTitle);
|
|
50976
|
+
const clickOpened = await isCartRevealed(page, expectedProductTitle, ctx);
|
|
50950
50977
|
if (clickOpened) {
|
|
50951
50978
|
dlog2(ctx, ` openMinicart: click opened drawer (${clickOpened})`);
|
|
50952
50979
|
return { method: "click", url: afterClickUrl, visibleMarker: clickOpened };
|
|
@@ -50957,7 +50984,7 @@ async function openMinicart(page, trigger, ctx, expectedProductTitle) {
|
|
|
50957
50984
|
return;
|
|
50958
50985
|
});
|
|
50959
50986
|
await page.waitForTimeout(1500);
|
|
50960
|
-
const hoverOpened = await isCartRevealed(page, expectedProductTitle);
|
|
50987
|
+
const hoverOpened = await isCartRevealed(page, expectedProductTitle, ctx);
|
|
50961
50988
|
if (hoverOpened) {
|
|
50962
50989
|
dlog2(ctx, ` openMinicart: hover opened drawer (${hoverOpened})`);
|
|
50963
50990
|
return { method: "hover", url: page.url(), visibleMarker: hoverOpened };
|
|
@@ -51003,7 +51030,16 @@ function titlesMatch(observed, expected) {
|
|
|
51003
51030
|
return false;
|
|
51004
51031
|
}
|
|
51005
51032
|
async function validateCartContainsTitle(page, expectedTitle, ctx) {
|
|
51033
|
+
const panelScopedTitleSelectors = selFor(ctx, "minicartPanel").flatMap((p) => [
|
|
51034
|
+
`${p} a[href*='/p']`,
|
|
51035
|
+
`${p} [class*='name' i]`,
|
|
51036
|
+
`${p} [class*='title' i]`,
|
|
51037
|
+
`${p} [class*='product' i]`,
|
|
51038
|
+
`${p} [data-cart-item-name]`,
|
|
51039
|
+
`${p} [data-qa-product-name]`
|
|
51040
|
+
]);
|
|
51006
51041
|
const titleSelectors = [
|
|
51042
|
+
...panelScopedTitleSelectors,
|
|
51007
51043
|
"[data-cart-item-name]",
|
|
51008
51044
|
"[data-cart-item] [class*='title' i]",
|
|
51009
51045
|
"[data-cart-item] [class*='name' i]",
|
|
@@ -51861,7 +51897,7 @@ async function flowPurchaseJourney(ctx) {
|
|
|
51861
51897
|
let cartOpenMethod = "failed";
|
|
51862
51898
|
let miniText = "";
|
|
51863
51899
|
let cartRevealMode = "unknown";
|
|
51864
|
-
const drawerAlreadyOpen = await isCartRevealed(page, expectedProductTitle) !== null;
|
|
51900
|
+
const drawerAlreadyOpen = await isCartRevealed(page, expectedProductTitle, ctx) !== null;
|
|
51865
51901
|
if (miniHit) {
|
|
51866
51902
|
miniText = await miniHit.locator.innerText().catch(() => "");
|
|
51867
51903
|
cartRevealMode = await detectCartRevealMode(page, miniHit.locator, drawerAlreadyOpen, ctx.viewport).catch(() => "unknown");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/parity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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",
|