@sentinelqa/playwright-reporter 0.1.29 → 0.1.30

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/README.md CHANGED
@@ -12,6 +12,8 @@ Works locally out of the box with no account required.
12
12
  Optionally upload runs to Sentinel Cloud for CI history and AI failure analysis.
13
13
 
14
14
  ![Sentinel Report Example](./docs/screenshot.png)
15
+ ![Run-to-Run Diff](./docs/run_diff.png)
16
+ ![CLI Quick Diagnosis](./docs/CLI.png)
15
17
 
16
18
  ## Features
17
19
 
@@ -362,6 +362,20 @@ const groupSimilarFailures = (tests) => {
362
362
  .filter((group) => group.tests.length > 1)
363
363
  .sort((a, b) => b.tests.length - a.tests.length);
364
364
  };
365
+ const groupFailureDigest = (tests) => {
366
+ const groups = new Map();
367
+ for (const test of getFailureTests(tests)) {
368
+ const summary = test.diagnosis
369
+ ? (0, quickDiagnosis_1.describeFailure)(test.diagnosis)
370
+ : (test.errors[0]?.split(/\r?\n/)[0]?.trim() || "Open the failure details to inspect the exact Playwright error.");
371
+ const key = summary;
372
+ if (!groups.has(key)) {
373
+ groups.set(key, { key, summary, tests: [] });
374
+ }
375
+ groups.get(key).tests.push(test);
376
+ }
377
+ return Array.from(groups.values()).sort((a, b) => b.tests.length - a.tests.length);
378
+ };
365
379
  const buildRunSnapshot = (tests, summary) => ({
366
380
  generatedAt: new Date().toISOString(),
367
381
  branch: getCurrentBranch(),
@@ -601,24 +615,30 @@ const renderFailureDigest = (tests) => {
601
615
  if (!failedTests.length) {
602
616
  return `<div class="empty-state">No failed tests were detected in this run.</div>`;
603
617
  }
618
+ const digestGroups = groupFailureDigest(tests);
604
619
  return `
605
620
  <div class="digest-grid">
606
- ${failedTests
607
- .map((test) => {
608
- const diagnosis = test.diagnosis;
609
- const title = escapeHtml(test.title);
610
- const summary = escapeHtml(diagnosis
611
- ? (0, quickDiagnosis_1.describeFailure)(diagnosis)
612
- : (test.errors[0]?.split(/\r?\n/)[0]?.trim() || "Open the failure details to inspect the exact Playwright error."));
613
- const debugSummary = escapeHtml(diagnosis
614
- ? (0, quickDiagnosis_1.buildDebugSummary)(diagnosis)
615
- : `Test: ${test.title}\nDiagnosis: Review trace and error details in the expanded card.`);
621
+ ${digestGroups
622
+ .map((group) => {
623
+ const primary = group.tests[0];
624
+ const diagnosis = primary.diagnosis;
625
+ const debugSummary = escapeHtml(group.tests.length > 1
626
+ ? [
627
+ `Grouped failure summary: ${group.summary}`,
628
+ ...group.tests.map((test) => `- ${test.title}`)
629
+ ].join("\n")
630
+ : diagnosis
631
+ ? (0, quickDiagnosis_1.buildDebugSummary)(diagnosis)
632
+ : `Test: ${primary.title}\nDiagnosis: Review trace and error details in the expanded card.`);
633
+ const uniqueLocators = Array.from(new Set(group.tests.map((test) => test.diagnosis?.locator).filter(Boolean)));
616
634
  return `
617
635
  <article class="digest-card">
618
636
  <div class="digest-head">
619
637
  <div>
620
- <span class="artifact-kind">${escapeHtml(test.status)}</span>
621
- <h3>${title}</h3>
638
+ <span class="artifact-kind">${escapeHtml(primary.status)}</span>
639
+ <h3>${group.tests.length > 1
640
+ ? `${group.tests.length} tests share this failure`
641
+ : escapeHtml(primary.title)}</h3>
622
642
  </div>
623
643
  <button
624
644
  type="button"
@@ -629,12 +649,20 @@ const renderFailureDigest = (tests) => {
629
649
  Copy summary
630
650
  </button>
631
651
  </div>
632
- <p>${summary}</p>
652
+ <p>${escapeHtml(group.summary)}</p>
633
653
  <div class="fact-row">
634
- ${diagnosis?.locator ? `<span class="fact-chip">Locator: ${escapeHtml(diagnosis.locator)}</span>` : ""}
635
- ${diagnosis?.expected ? `<span class="fact-chip">Expected: ${escapeHtml(diagnosis.expected)}</span>` : ""}
636
- ${diagnosis?.received ? `<span class="fact-chip">Observed: ${escapeHtml(diagnosis.received)}</span>` : ""}
654
+ ${diagnosis?.expected && diagnosis?.received
655
+ ? `<span class="fact-chip">Expected: ${escapeHtml(diagnosis.expected)}</span><span class="fact-chip">Observed: ${escapeHtml(diagnosis.received)}</span>`
656
+ : ""}
657
+ ${uniqueLocators.length === 1
658
+ ? `<span class="fact-chip">Locator: ${escapeHtml(uniqueLocators[0])}</span>`
659
+ : uniqueLocators.length > 1
660
+ ? `<span class="fact-chip">${uniqueLocators.length} locators involved</span>`
661
+ : ""}
637
662
  </div>
663
+ ${group.tests.length > 1
664
+ ? `<ul class="group-list">${group.tests.map((test) => `<li>${escapeHtml(test.title)}</li>`).join("\n")}</ul>`
665
+ : ""}
638
666
  </article>
639
667
  `;
640
668
  })
@@ -28,11 +28,11 @@ const toMessage = (result) => {
28
28
  };
29
29
  const classifySignal = (message) => {
30
30
  const lower = message.toLowerCase();
31
- if (/timeout|timed out|waiting for/.test(lower))
32
- return "timeout";
33
31
  if (/expected substring|expected string|received string|tohavetext|tocontaintext/.test(lower)) {
34
32
  return "assertion_mismatch";
35
33
  }
34
+ if (/timeout|timed out|waiting for/.test(lower))
35
+ return "timeout";
36
36
  if (/resolved to 0 elements|locator.*not found|never appeared|strict mode violation/.test(lower)) {
37
37
  return "locator_not_found";
38
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentinelqa/playwright-reporter",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "private": false,
5
5
  "description": "Playwright reporter for CI debugging with optional Sentinel cloud dashboards",
6
6
  "license": "MIT",