@decocms/parity 0.11.4 → 0.11.6
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 +27 -0
- package/dist/cli.js +290 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.11.6](https://github.com/decocms/parity/compare/v0.11.5...v0.11.6) (2026-06-17)
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* **New `plp-pagination` check.** Tests that `?page=2` and `?page=3` of every captured PLP return 200 AND show different products than page 1. Catches the classic migration regression: the new site silently ignores `?page=N` and returns the first page on every request (or caps pagination). Live tested against bagaggio TanStack — surfaces a real critical bug (`page=2` shows the same 10 products as `page=1`, 100% overlap). Falls back to scraping the home page for a category link when running standalone (`parity check plp-pagination`).
|
|
13
|
+
* **Cross-side count divergence detection.** When prod and cand both serve a paginated PLP but cand's `?page=2` product count differs from prod's by more than 30%, flag as medium-severity (likely sort-order or index pruning regression).
|
|
14
|
+
|
|
15
|
+
## [0.11.5](https://github.com/decocms/parity/compare/v0.11.4...v0.11.5) (2026-06-17)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
* **Variant clicks now wait for navigation.** Deco TanStack PDPs render size/color pickers as `<a href="<product>/p?skuId=N">` links — clicking navigates to a different SKU URL. The previous `click + waitForTimeout(400)` ran add-to-cart against the pre-nav page where the variant was still "unselected". New `clickAndMaybeWait` helper races the click with `page.waitForNavigation` so the flow runs against the post-navigation page when it happens, and falls through when it doesn't (button-radio case).
|
|
20
|
+
* **`validateAddToCart` now uses `cartOpenedIndicator` selectors.** The new selector key from 0.11.4 (`[aria-label='Fechar notificação']` / `[aria-label='Fechar carrinho']` + generic role fallbacks) is now actually wired into the post-click drawer-open probe, so Deco TanStack notifications correctly mark add-to-cart as `ok` via the `drawer-open` signal.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
* **Landing-page detection before LLM recovery on missing buy button.** When step 6 (add-to-cart) can't find a buy button, the runner now checks whether the page even looks like a PDP (schema:Product JSON-LD, `<form>` with button, price text, variant inputs). If fewer than 2 PDP signals fire, the step is skipped with `PDP appears to be a landing page (...)` instead of burning LLM recovery budget on a page that has nothing to recover. Reasons are surfaced in the skip note so the user immediately knows why.
|
|
25
|
+
|
|
26
|
+
### Verification (live against bagaggio)
|
|
27
|
+
|
|
28
|
+
| Version | Steps reached on cand |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| 0.11.2 and earlier | 3/9 (stops at enter-pdp) |
|
|
31
|
+
| 0.11.3 | 4/9 (stops at select-variant) |
|
|
32
|
+
| 0.11.4 | 6/9 (stops at add-to-cart) |
|
|
33
|
+
| **0.11.5** | **9/9** (reaches go-checkout) |
|
|
34
|
+
|
|
8
35
|
## [0.11.4](https://github.com/decocms/parity/compare/v0.11.3...v0.11.4) (2026-06-17)
|
|
9
36
|
|
|
10
37
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -8534,6 +8534,225 @@ function hasReservedSpace($img) {
|
|
|
8534
8534
|
return false;
|
|
8535
8535
|
}
|
|
8536
8536
|
|
|
8537
|
+
// src/checks/plp-pagination.ts
|
|
8538
|
+
async function plpPagination(ctx) {
|
|
8539
|
+
const start = Date.now();
|
|
8540
|
+
const issues = [];
|
|
8541
|
+
let prodPlp = pickPlpUrl(ctx.prodFlows);
|
|
8542
|
+
let candPlp = pickPlpUrl(ctx.candFlows);
|
|
8543
|
+
if (!prodPlp && ctx.prodPages.length > 0) {
|
|
8544
|
+
prodPlp = await discoverPlpFromHome(ctx.prodPages[0].url);
|
|
8545
|
+
}
|
|
8546
|
+
if (!candPlp && ctx.candPages.length > 0) {
|
|
8547
|
+
candPlp = await discoverPlpFromHome(ctx.candPages[0].url);
|
|
8548
|
+
}
|
|
8549
|
+
if (!prodPlp && !candPlp) {
|
|
8550
|
+
return {
|
|
8551
|
+
name: "plp-pagination",
|
|
8552
|
+
status: "skipped",
|
|
8553
|
+
severity: "medium",
|
|
8554
|
+
durationMs: Date.now() - start,
|
|
8555
|
+
summary: "no PLP captured by purchase-journey step 2 AND no home page to discover one from — skipping",
|
|
8556
|
+
issues: []
|
|
8557
|
+
};
|
|
8558
|
+
}
|
|
8559
|
+
const data = {};
|
|
8560
|
+
for (const [side, plp] of [
|
|
8561
|
+
["prod", prodPlp],
|
|
8562
|
+
["cand", candPlp]
|
|
8563
|
+
]) {
|
|
8564
|
+
if (!plp)
|
|
8565
|
+
continue;
|
|
8566
|
+
const page1 = await fetchPlpProducts(plp);
|
|
8567
|
+
const page2 = await fetchPlpProducts(withPage(plp, 2));
|
|
8568
|
+
const page3 = await fetchPlpProducts(withPage(plp, 3));
|
|
8569
|
+
data[`${side}_page_counts`] = {
|
|
8570
|
+
page1: { status: page1.status, products: page1.productPaths.length },
|
|
8571
|
+
page2: { status: page2.status, products: page2.productPaths.length },
|
|
8572
|
+
page3: { status: page3.status, products: page3.productPaths.length }
|
|
8573
|
+
};
|
|
8574
|
+
for (const [n, result] of [
|
|
8575
|
+
[2, page2],
|
|
8576
|
+
[3, page3]
|
|
8577
|
+
]) {
|
|
8578
|
+
if (result.status !== 200) {
|
|
8579
|
+
issues.push({
|
|
8580
|
+
id: `plp-pagination:${side}:page${n}:status-${result.status}`,
|
|
8581
|
+
severity: "high",
|
|
8582
|
+
category: "functional",
|
|
8583
|
+
check: "plp-pagination",
|
|
8584
|
+
summary: `[${side}] page=${n} returned HTTP ${result.status} (expected 200) — pagination breaks under ?page=${n}`,
|
|
8585
|
+
page: withPage(plp, n)
|
|
8586
|
+
});
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
if (page1.productPaths.length > 0 && page2.status === 200) {
|
|
8590
|
+
const overlap = setOverlap(page1.productPaths, page2.productPaths);
|
|
8591
|
+
if (overlap > 0.9) {
|
|
8592
|
+
issues.push({
|
|
8593
|
+
id: `plp-pagination:${side}:page2-identical`,
|
|
8594
|
+
severity: "critical",
|
|
8595
|
+
category: "functional",
|
|
8596
|
+
check: "plp-pagination",
|
|
8597
|
+
summary: `[${side}] page=2 shows the same ${page1.productPaths.length} products as page=1 (${(overlap * 100).toFixed(0)}% overlap) — ?page=N is being ignored`,
|
|
8598
|
+
page: withPage(plp, 2),
|
|
8599
|
+
details: `Page 1 first product: ${page1.productPaths[0] ?? "(none)"}
|
|
8600
|
+
Page 2 first product: ${page2.productPaths[0] ?? "(none)"}`
|
|
8601
|
+
});
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
if (page2.productPaths.length > 0 && page3.status === 200) {
|
|
8605
|
+
const overlap = setOverlap(page2.productPaths, page3.productPaths);
|
|
8606
|
+
if (overlap > 0.9) {
|
|
8607
|
+
issues.push({
|
|
8608
|
+
id: `plp-pagination:${side}:page3-identical`,
|
|
8609
|
+
severity: "critical",
|
|
8610
|
+
category: "functional",
|
|
8611
|
+
check: "plp-pagination",
|
|
8612
|
+
summary: `[${side}] page=3 shows the same products as page=2 (${(overlap * 100).toFixed(0)}% overlap) — pagination capped or broken`,
|
|
8613
|
+
page: withPage(plp, 3)
|
|
8614
|
+
});
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
if (prodPlp && candPlp) {
|
|
8619
|
+
const prodCounts = data.prod_page_counts;
|
|
8620
|
+
const candCounts = data.cand_page_counts;
|
|
8621
|
+
if (prodCounts && candCounts) {
|
|
8622
|
+
const p = prodCounts.page2.products;
|
|
8623
|
+
const c = candCounts.page2.products;
|
|
8624
|
+
if (p > 0 && Math.abs(p - c) / p > 0.3) {
|
|
8625
|
+
issues.push({
|
|
8626
|
+
id: "plp-pagination:cross-side-count-divergence",
|
|
8627
|
+
severity: "medium",
|
|
8628
|
+
category: "functional",
|
|
8629
|
+
check: "plp-pagination",
|
|
8630
|
+
summary: `page=2 product count diverges: prod=${p}, cand=${c} (Δ ${Math.abs(p - c)}, >30%)`
|
|
8631
|
+
});
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
const status = issues.some((i) => i.severity === "critical") ? "fail" : issues.length > 0 ? "fail" : "pass";
|
|
8636
|
+
return {
|
|
8637
|
+
name: "plp-pagination",
|
|
8638
|
+
status,
|
|
8639
|
+
severity: "high",
|
|
8640
|
+
durationMs: Date.now() - start,
|
|
8641
|
+
summary: issues.length === 0 ? `PLP pagination working on both sides (tested page=2 + page=3)` : `${issues.length} pagination issue(s) — see details`,
|
|
8642
|
+
issues,
|
|
8643
|
+
data
|
|
8644
|
+
};
|
|
8645
|
+
}
|
|
8646
|
+
async function fetchPlpProducts(url) {
|
|
8647
|
+
try {
|
|
8648
|
+
const res = await fetch(url, {
|
|
8649
|
+
headers: {
|
|
8650
|
+
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
|
|
8651
|
+
},
|
|
8652
|
+
signal: AbortSignal.timeout(15000)
|
|
8653
|
+
});
|
|
8654
|
+
if (res.status !== 200) {
|
|
8655
|
+
return { status: res.status, productPaths: [] };
|
|
8656
|
+
}
|
|
8657
|
+
const html = await res.text();
|
|
8658
|
+
const paths = new Set;
|
|
8659
|
+
const re = /href="([^"]+\/p(?:\?[^"]*|\/[^"]+|))"/gi;
|
|
8660
|
+
let m;
|
|
8661
|
+
while (m = re.exec(html)) {
|
|
8662
|
+
const path = m[1];
|
|
8663
|
+
if (!path)
|
|
8664
|
+
continue;
|
|
8665
|
+
const normalized = path.split("?")[0].replace(/\/$/, "");
|
|
8666
|
+
paths.add(normalized);
|
|
8667
|
+
}
|
|
8668
|
+
const re2 = /href="([^"]+\/products\/[^"]+)"/gi;
|
|
8669
|
+
while (m = re2.exec(html)) {
|
|
8670
|
+
const path = m[1];
|
|
8671
|
+
if (path)
|
|
8672
|
+
paths.add(path.split("?")[0].replace(/\/$/, ""));
|
|
8673
|
+
}
|
|
8674
|
+
return { status: 200, productPaths: Array.from(paths) };
|
|
8675
|
+
} catch (err) {
|
|
8676
|
+
return { status: 0, productPaths: [] };
|
|
8677
|
+
}
|
|
8678
|
+
}
|
|
8679
|
+
function withPage(url, n) {
|
|
8680
|
+
try {
|
|
8681
|
+
const u = new URL(url);
|
|
8682
|
+
u.searchParams.set("page", String(n));
|
|
8683
|
+
return u.toString();
|
|
8684
|
+
} catch {
|
|
8685
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
8686
|
+
return `${url}${sep}page=${n}`;
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
async function discoverPlpFromHome(homeUrl) {
|
|
8690
|
+
try {
|
|
8691
|
+
const res = await fetch(homeUrl, {
|
|
8692
|
+
headers: {
|
|
8693
|
+
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
|
|
8694
|
+
},
|
|
8695
|
+
signal: AbortSignal.timeout(15000)
|
|
8696
|
+
});
|
|
8697
|
+
if (!res.ok)
|
|
8698
|
+
return null;
|
|
8699
|
+
const html = await res.text();
|
|
8700
|
+
const candidates = new Set;
|
|
8701
|
+
const re = /href="([^"]+)"/gi;
|
|
8702
|
+
let m;
|
|
8703
|
+
while (m = re.exec(html)) {
|
|
8704
|
+
const href = m[1];
|
|
8705
|
+
if (!href)
|
|
8706
|
+
continue;
|
|
8707
|
+
if (/^https?:\/\//.test(href) || href.startsWith("#") || href === "/" || /\/(p|cart|carrinho|checkout|account|conta|login|wishlist|favoritos)(\/|$|\?)/i.test(href) || /\.(jpg|png|webp|css|js|svg|ico|woff)/i.test(href)) {
|
|
8708
|
+
continue;
|
|
8709
|
+
}
|
|
8710
|
+
const segments = href.split("?")[0].split("/").filter(Boolean);
|
|
8711
|
+
if (segments.length === 0 || segments.length > 3)
|
|
8712
|
+
continue;
|
|
8713
|
+
candidates.add(href.split("?")[0]);
|
|
8714
|
+
}
|
|
8715
|
+
const sorted = Array.from(candidates).sort((a, b) => a.length - b.length);
|
|
8716
|
+
const home = new URL(homeUrl);
|
|
8717
|
+
const pick = sorted[0];
|
|
8718
|
+
if (!pick)
|
|
8719
|
+
return null;
|
|
8720
|
+
return new URL(pick, home).toString();
|
|
8721
|
+
} catch {
|
|
8722
|
+
return null;
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
function pickPlpUrl(flows) {
|
|
8726
|
+
for (const fc of flows) {
|
|
8727
|
+
if (fc.flow !== "purchase-journey")
|
|
8728
|
+
continue;
|
|
8729
|
+
const plpStep = fc.steps?.find((s) => s.name === "navigate-plp" && s.status === "ok");
|
|
8730
|
+
if (plpStep?.url)
|
|
8731
|
+
return plpStep.url;
|
|
8732
|
+
const plpPage = fc.pages.find((p) => {
|
|
8733
|
+
try {
|
|
8734
|
+
const u = new URL(p.url);
|
|
8735
|
+
return u.pathname.split("/").filter(Boolean).length <= 2 && !/\/p$|\/products\//.test(u.pathname);
|
|
8736
|
+
} catch {
|
|
8737
|
+
return false;
|
|
8738
|
+
}
|
|
8739
|
+
});
|
|
8740
|
+
if (plpPage)
|
|
8741
|
+
return plpPage.url;
|
|
8742
|
+
}
|
|
8743
|
+
return null;
|
|
8744
|
+
}
|
|
8745
|
+
function setOverlap(a, b) {
|
|
8746
|
+
if (a.length === 0 || b.length === 0)
|
|
8747
|
+
return 0;
|
|
8748
|
+
const sa = new Set(a);
|
|
8749
|
+
let common = 0;
|
|
8750
|
+
for (const x of b)
|
|
8751
|
+
if (sa.has(x))
|
|
8752
|
+
common++;
|
|
8753
|
+
return common / Math.max(a.length, b.length);
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8537
8756
|
// src/checks/purchase-journey-flow.ts
|
|
8538
8757
|
var STEP_LABELS3 = {
|
|
8539
8758
|
"visit-home": "Visitar home",
|
|
@@ -10515,7 +10734,8 @@ var ALL_CHECKS = [
|
|
|
10515
10734
|
pdpGalleryRelated,
|
|
10516
10735
|
footerLinksHealth,
|
|
10517
10736
|
loginFlow,
|
|
10518
|
-
pictureMissingDims
|
|
10737
|
+
pictureMissingDims,
|
|
10738
|
+
plpPagination
|
|
10519
10739
|
];
|
|
10520
10740
|
var ALL_CHECKS_BY_NAME = {
|
|
10521
10741
|
"http-status-parity": httpStatusParity,
|
|
@@ -10542,7 +10762,8 @@ var ALL_CHECKS_BY_NAME = {
|
|
|
10542
10762
|
"pdp-gallery-related": pdpGalleryRelated,
|
|
10543
10763
|
"footer-links-health": footerLinksHealth,
|
|
10544
10764
|
"login-flow": loginFlow,
|
|
10545
|
-
"picture-missing-dims": pictureMissingDims
|
|
10765
|
+
"picture-missing-dims": pictureMissingDims,
|
|
10766
|
+
"plp-pagination": plpPagination
|
|
10546
10767
|
};
|
|
10547
10768
|
var FLOW_DEPENDENT_CHECKS = new Set([
|
|
10548
10769
|
"purchase-journey-flow",
|
|
@@ -12776,12 +12997,20 @@ async function flowPurchaseJourney(ctx) {
|
|
|
12776
12997
|
reportStart(6, "add-to-cart");
|
|
12777
12998
|
let buyHit = await firstVisibleLocator(page, selFor(ctx, "buyButton"));
|
|
12778
12999
|
let buyRecovered = false;
|
|
12779
|
-
if (!buyHit
|
|
12780
|
-
const
|
|
12781
|
-
if (
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
13000
|
+
if (!buyHit) {
|
|
13001
|
+
const landingCheck = await detectLandingPage(page);
|
|
13002
|
+
if (landingCheck.isLanding) {
|
|
13003
|
+
steps.push(makeSkipStep(6, "add-to-cart", ctx, `PDP appears to be a landing page (${landingCheck.reasons.join("; ")}) — no buy form to test`));
|
|
13004
|
+
reportEnd(6, "add-to-cart", "skipped", 0, "landing page detected");
|
|
13005
|
+
return { pages, steps };
|
|
13006
|
+
}
|
|
13007
|
+
if (budget.remaining > 0) {
|
|
13008
|
+
const recovery = await attemptRecovery(page, ctx, "add-to-cart", "Clicar no botão de comprar/adicionar ao carrinho", selFor(ctx, "buyButton"));
|
|
13009
|
+
if (recovery) {
|
|
13010
|
+
buyHit = recovery;
|
|
13011
|
+
buyRecovered = true;
|
|
13012
|
+
budget.remaining--;
|
|
13013
|
+
}
|
|
12785
13014
|
}
|
|
12786
13015
|
}
|
|
12787
13016
|
if (!buyHit) {
|
|
@@ -13232,6 +13461,56 @@ async function fillCep(page, selector, cep) {
|
|
|
13232
13461
|
return false;
|
|
13233
13462
|
}
|
|
13234
13463
|
}
|
|
13464
|
+
async function detectLandingPage(page) {
|
|
13465
|
+
const reasons = [];
|
|
13466
|
+
let pdpSignalCount = 0;
|
|
13467
|
+
try {
|
|
13468
|
+
const hasSchema = await page.locator(`script[type='application/ld+json']:has-text('"@type":"Product"')`).first().count().catch(() => 0);
|
|
13469
|
+
if (hasSchema > 0)
|
|
13470
|
+
pdpSignalCount++;
|
|
13471
|
+
else
|
|
13472
|
+
reasons.push("no schema:Product JSON-LD");
|
|
13473
|
+
} catch {
|
|
13474
|
+
reasons.push("no schema:Product JSON-LD");
|
|
13475
|
+
}
|
|
13476
|
+
try {
|
|
13477
|
+
const hasItemtype = await page.locator("[itemtype*='Product']").first().count();
|
|
13478
|
+
if (hasItemtype > 0)
|
|
13479
|
+
pdpSignalCount++;
|
|
13480
|
+
} catch {}
|
|
13481
|
+
try {
|
|
13482
|
+
const hasForm = await page.locator("form:has(button)").first().count();
|
|
13483
|
+
if (hasForm > 0)
|
|
13484
|
+
pdpSignalCount++;
|
|
13485
|
+
else
|
|
13486
|
+
reasons.push("no <form> with button");
|
|
13487
|
+
} catch {
|
|
13488
|
+
reasons.push("no <form> with button");
|
|
13489
|
+
}
|
|
13490
|
+
try {
|
|
13491
|
+
const bodyText = await page.locator("body").innerText({ timeout: 500 });
|
|
13492
|
+
if (/R\$\s*\d+|\$\s*\d+\.\d{2}|€\s*\d+|\bUSD\s*\d+/i.test(bodyText)) {
|
|
13493
|
+
pdpSignalCount++;
|
|
13494
|
+
} else {
|
|
13495
|
+
reasons.push("no price text (R$ / $ / €)");
|
|
13496
|
+
}
|
|
13497
|
+
} catch {
|
|
13498
|
+
reasons.push("no price text (R$ / $ / €)");
|
|
13499
|
+
}
|
|
13500
|
+
try {
|
|
13501
|
+
const hasVariantInput = await page.locator("select, input[type='number'], input[type='radio']").first().count();
|
|
13502
|
+
if (hasVariantInput > 0)
|
|
13503
|
+
pdpSignalCount++;
|
|
13504
|
+
} catch {}
|
|
13505
|
+
return { isLanding: pdpSignalCount < 2, reasons };
|
|
13506
|
+
}
|
|
13507
|
+
async function clickAndMaybeWait(page, locator, _label) {
|
|
13508
|
+
await Promise.allSettled([
|
|
13509
|
+
page.waitForNavigation({ timeout: 5000, waitUntil: "domcontentloaded" }),
|
|
13510
|
+
locator.click({ timeout: 2000 })
|
|
13511
|
+
]);
|
|
13512
|
+
await page.waitForTimeout(600);
|
|
13513
|
+
}
|
|
13235
13514
|
async function selectVariant(page, ctx) {
|
|
13236
13515
|
const actions = [];
|
|
13237
13516
|
let primarySelectorKey;
|
|
@@ -13275,10 +13554,7 @@ async function selectVariant(page, ctx) {
|
|
|
13275
13554
|
const sizeHit = await firstVisibleLocator(page, selFor(ctx, "sizeSwatch"));
|
|
13276
13555
|
if (sizeHit && !await sizeHit.locator.isDisabled().catch(() => false)) {
|
|
13277
13556
|
const sizeText = (await sizeHit.locator.innerText().catch(() => "")).slice(0, 20).trim();
|
|
13278
|
-
await sizeHit.locator
|
|
13279
|
-
return;
|
|
13280
|
-
});
|
|
13281
|
-
await page.waitForTimeout(400);
|
|
13557
|
+
await clickAndMaybeWait(page, sizeHit.locator, "sizeSwatch");
|
|
13282
13558
|
actions.push(`Selecionou tamanho${sizeText ? ` '${sizeText}'` : ""} (\`${sizeHit.selector}\`)`);
|
|
13283
13559
|
trackPrimary("sizeSwatch", sizeHit.selector);
|
|
13284
13560
|
}
|
|
@@ -13287,10 +13563,7 @@ async function selectVariant(page, ctx) {
|
|
|
13287
13563
|
if (colorHit && !await colorHit.locator.isDisabled().catch(() => false)) {
|
|
13288
13564
|
const colorText = (await colorHit.locator.innerText().catch(() => "")).slice(0, 20).trim();
|
|
13289
13565
|
const colorLabel = colorText || await colorHit.locator.getAttribute("aria-label").catch(() => null) || "";
|
|
13290
|
-
await colorHit.locator
|
|
13291
|
-
return;
|
|
13292
|
-
});
|
|
13293
|
-
await page.waitForTimeout(400);
|
|
13566
|
+
await clickAndMaybeWait(page, colorHit.locator, "colorSwatch");
|
|
13294
13567
|
actions.push(`Selecionou cor${colorLabel ? ` '${colorLabel}'` : ""} (\`${colorHit.selector}\`)`);
|
|
13295
13568
|
trackPrimary("colorSwatch", colorHit.selector);
|
|
13296
13569
|
}
|
|
@@ -13441,6 +13714,7 @@ async function validateAddToCart(page, ctx, cartCountBefore, beforeUrl) {
|
|
|
13441
13714
|
};
|
|
13442
13715
|
}
|
|
13443
13716
|
const drawerSelectors = [
|
|
13717
|
+
...selFor(ctx, "cartOpenedIndicator"),
|
|
13444
13718
|
"[role='dialog']",
|
|
13445
13719
|
"[data-minicart][aria-expanded='true']",
|
|
13446
13720
|
"[data-cart-drawer].open",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/parity",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
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",
|