@decocms/parity 0.17.0 → 0.17.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +83 -30
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -39020,6 +39020,21 @@ async function capturePage(page, opts) {
39020
39020
  let xRobotsTag = null;
39021
39021
  let vitals = null;
39022
39022
  let html3 = "";
39023
+ let _resolveDisconnect;
39024
+ const disconnectPromise = new Promise((r) => {
39025
+ _resolveDisconnect = r;
39026
+ });
39027
+ const browser = page.context().browser();
39028
+ const handleBrowserDisconnect = () => {
39029
+ dlog(opts.side, opts.viewport, " capturePage: browser disconnected — aborting capture");
39030
+ _resolveDisconnect();
39031
+ };
39032
+ const handlePageCrash = () => {
39033
+ dlog(opts.side, opts.viewport, " capturePage: page crashed — aborting capture");
39034
+ _resolveDisconnect();
39035
+ };
39036
+ browser?.on("disconnected", handleBrowserDisconnect);
39037
+ page.on("crash", handlePageCrash);
39023
39038
  const inner = async () => {
39024
39039
  try {
39025
39040
  dlog(opts.side, opts.viewport, ` capturePage: goto(${opts.url}) start`);
@@ -39130,7 +39145,7 @@ async function capturePage(page, opts) {
39130
39145
  };
39131
39146
  const SAFETY_MARGIN_MS = 1e4;
39132
39147
  const outerDeadlineMs = overallBudgetMs + SAFETY_MARGIN_MS;
39133
- return Promise.race([
39148
+ const result = await Promise.race([
39134
39149
  inner(),
39135
39150
  new Promise((resolve) => setTimeout(() => {
39136
39151
  state.console.push({
@@ -39138,8 +39153,18 @@ async function capturePage(page, opts) {
39138
39153
  text: `[capture-timeout] capturePage exceeded ${outerDeadlineMs}ms outer deadline — returning partial capture`
39139
39154
  });
39140
39155
  resolve(buildPartial());
39141
- }, outerDeadlineMs))
39156
+ }, outerDeadlineMs)),
39157
+ disconnectPromise.then(() => {
39158
+ state.console.push({
39159
+ type: "error",
39160
+ text: "[browser-disconnected] browser/page crashed — returning partial capture"
39161
+ });
39162
+ return buildPartial();
39163
+ })
39142
39164
  ]);
39165
+ browser?.off("disconnected", handleBrowserDisconnect);
39166
+ page.off("crash", handlePageCrash);
39167
+ return result;
39143
39168
  }
39144
39169
 
39145
39170
  // src/report/html-template.ts
@@ -49348,6 +49373,7 @@ async function cssTraceCommand(opts) {
49348
49373
 
49349
49374
  // src/commands/e2e.ts
49350
49375
  import { writeFileSync as writeFileSync10 } from "node:fs";
49376
+ import { spawn } from "node:child_process";
49351
49377
  import chalk8 from "chalk";
49352
49378
  import ora4 from "ora";
49353
49379
 
@@ -51232,6 +51258,16 @@ async function validateCartContainsTitle(page, expectedTitle, ctx) {
51232
51258
  await page.waitForTimeout(2000);
51233
51259
  observed = await sweepTitles();
51234
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
+ }
51235
51271
  dlog2(ctx, ` validateCartContainsTitle: observed ${observed.length} titles`);
51236
51272
  if (observed.length === 0) {
51237
51273
  return { found: false, observedTitles: [], method: "none" };
@@ -52010,44 +52046,45 @@ async function flowPurchaseJourney(ctx) {
52010
52046
  const beforeUrl7 = page.url();
52011
52047
  const spBefore7 = screenshotPath(ctx, "pj-7-minicart-before");
52012
52048
  await screenshotStable(page, { path: spBefore7, fullPage: false });
52013
- let miniHit = await firstVisibleLocator(page, selFor(ctx, "minicartTrigger"));
52014
- let miniRecovered = false;
52015
- if (!miniHit && budget.remaining > 0) {
52016
- const recovery = await attemptRecovery(page, ctx, "open-minicart", "Abrir o minicart/drawer do carrinho", selFor(ctx, "minicartTrigger"));
52017
- if (recovery) {
52018
- miniHit = recovery;
52019
- miniRecovered = true;
52020
- budget.remaining--;
52021
- }
52022
- }
52023
52049
  let cartOpenMethod = "failed";
52024
52050
  let miniText = "";
52025
52051
  let cartRevealMode = "unknown";
52026
52052
  let revealDiagnostics;
52027
52053
  const drawerAlreadyOpen = await isCartRevealed(page, expectedProductTitle, ctx) !== null;
52028
- if (miniHit) {
52029
- miniText = await miniHit.locator.innerText().catch(() => "");
52030
- cartRevealMode = await detectCartRevealMode(page, miniHit.locator, drawerAlreadyOpen, ctx.viewport).catch(() => "unknown");
52031
- dlog2(ctx, `step 7 open-minicart: cartRevealMode=${cartRevealMode}`);
52032
- let openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52033
- cartOpenMethod = openResult.method;
52034
- revealDiagnostics = openResult.diagnostics;
52035
- if (cartOpenMethod === "failed" && revealDiagnostics && budget.remaining > 0) {
52036
- 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"));
52037
52064
  if (recovery) {
52038
- budget.remaining--;
52039
52065
  miniHit = recovery;
52040
52066
  miniRecovered = true;
52041
- openResult = await openMinicart(page, miniHit, ctx, expectedProductTitle);
52042
- cartOpenMethod = openResult.method;
52043
- revealDiagnostics = openResult.diagnostics;
52067
+ budget.remaining--;
52044
52068
  }
52045
52069
  }
52046
- } else {
52047
- if (drawerAlreadyOpen) {
52048
- cartOpenMethod = "already-open";
52049
- cartRevealMode = "inline-notification";
52050
- 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
+ }
52051
52088
  }
52052
52089
  }
52053
52090
  let step7Validation;
@@ -55147,6 +55184,19 @@ async function e2eCommand(opts) {
55147
55184
  quiet: opts.json === true
55148
55185
  });
55149
55186
  }
55187
+ const E2E_TIMEOUT_MS = 3 * 60 * 1000;
55188
+ const watchdogScript = `setTimeout(()=>{try{process.kill(${process.pid},9)}catch(_){}},${E2E_TIMEOUT_MS})`;
55189
+ const watchdog = spawn(process.execPath, ["-e", watchdogScript], {
55190
+ stdio: "ignore",
55191
+ detached: false
55192
+ });
55193
+ watchdog.unref();
55194
+ const onSignal = () => {
55195
+ watchdog.kill();
55196
+ process.exit(1);
55197
+ };
55198
+ process.once("SIGINT", onSignal);
55199
+ process.once("SIGTERM", onSignal);
55150
55200
  const spinner = opts.json ? null : ora4("Lançando browser…").start();
55151
55201
  let browser = null;
55152
55202
  const allFlows = [];
@@ -55190,6 +55240,9 @@ async function e2eCommand(opts) {
55190
55240
  if (spinner)
55191
55241
  spinner.succeed(`${allFlows.length} flow capture(s), ${allPages.length} página(s)`);
55192
55242
  } finally {
55243
+ watchdog.kill();
55244
+ process.off("SIGINT", onSignal);
55245
+ process.off("SIGTERM", onSignal);
55193
55246
  await browser?.close().catch(() => {
55194
55247
  return;
55195
55248
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/parity",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
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",