@reportforge/playwright-pdf 0.8.0 → 0.8.1

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/dist/index.js CHANGED
@@ -11407,6 +11407,7 @@ var DEFAULT_MAX_STEPS = 50;
11407
11407
  var DEFAULT_MAX_CONSOLE_LINES = 50;
11408
11408
  var PW_API_WINDOW = 10;
11409
11409
  var isTeardown = (category) => category === "hook" || category === "fixture";
11410
+ var inHookSubtree = (flat, f) => isTeardown(f.category) || f.ancestors.some((a) => isTeardown(flat[a].category));
11410
11411
  function flatten(steps) {
11411
11412
  const out = [];
11412
11413
  const walk = (nodes, depth, ancestors) => {
@@ -11425,7 +11426,7 @@ function flatten(steps) {
11425
11426
  walk(steps ?? [], 0, []);
11426
11427
  return out;
11427
11428
  }
11428
- function materialize(n) {
11429
+ function materialize(n, isFailing) {
11429
11430
  const loc = n.raw.location;
11430
11431
  return {
11431
11432
  title: clampWithEllipsis(n.raw.title ?? "", MAX_TITLE_CHARS),
@@ -11433,17 +11434,16 @@ function materialize(n) {
11433
11434
  durationMs: Math.max(0, Math.round(n.raw.duration ?? 0)),
11434
11435
  depth: n.depth,
11435
11436
  failed: n.hasError,
11436
- errorMessage: n.raw.error?.message ? clampWithEllipsis(stripAnsi(n.raw.error.message).split("\n")[0], MAX_TITLE_CHARS) : void 0,
11437
+ errorMessage: isFailing && n.raw.error?.message ? clampWithEllipsis(stripAnsi(n.raw.error.message).split("\n")[0], MAX_TITLE_CHARS) : void 0,
11437
11438
  location: loc?.file != null ? `${loc.file}:${loc.line ?? 0}` : void 0
11438
11439
  };
11439
11440
  }
11440
11441
  function pickFailingIdx(flat) {
11441
- const inTeardown = (f) => isTeardown(f.category) || f.ancestors.some((a) => isTeardown(flat[a].category));
11442
11442
  const pick = (allowTeardown) => {
11443
11443
  let idx = -1;
11444
11444
  for (let i = 0; i < flat.length; i++) {
11445
11445
  const f = flat[i];
11446
- if (!f.hasError || !allowTeardown && inTeardown(f)) continue;
11446
+ if (!f.hasError || !allowTeardown && inHookSubtree(flat, f)) continue;
11447
11447
  if (idx === -1 || f.depth >= flat[idx].depth) idx = i;
11448
11448
  }
11449
11449
  return idx;
@@ -11466,7 +11466,7 @@ function compactSteps(rawSteps, opts = {}) {
11466
11466
  if (opts.apiSteps) {
11467
11467
  const pwApiBefore = [];
11468
11468
  for (let i = 0; i < failingIdx; i++) {
11469
- if (flat[i].category === "pw:api") pwApiBefore.push(i);
11469
+ if (flat[i].category === "pw:api" && !inHookSubtree(flat, flat[i])) pwApiBefore.push(i);
11470
11470
  }
11471
11471
  for (const i of pwApiBefore.slice(-PW_API_WINDOW)) keep.add(i);
11472
11472
  }
@@ -11482,7 +11482,7 @@ function compactSteps(rawSteps, opts = {}) {
11482
11482
  const mat = (i) => {
11483
11483
  let es = cache.get(i);
11484
11484
  if (!es) {
11485
- es = materialize(flat[i]);
11485
+ es = materialize(flat[i], i === failingIdx);
11486
11486
  cache.set(i, es);
11487
11487
  }
11488
11488
  return es;
@@ -13495,7 +13495,7 @@ var PdfReporter = class {
13495
13495
  this.pdfGenerator = new PdfGenerator();
13496
13496
  this.options = parseOptions(rawOptions);
13497
13497
  this.dataCollector = new DataCollector(this.options.capture);
13498
- const version = true ? "0.8.0" : "0.x";
13498
+ const version = true ? "0.8.1" : "0.x";
13499
13499
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
13500
13500
  this.licenseClient = new LicenseClient({
13501
13501
  licenseKey: this.options.licenseKey,
package/dist/index.mjs CHANGED
@@ -11408,6 +11408,7 @@ var DEFAULT_MAX_STEPS = 50;
11408
11408
  var DEFAULT_MAX_CONSOLE_LINES = 50;
11409
11409
  var PW_API_WINDOW = 10;
11410
11410
  var isTeardown = (category) => category === "hook" || category === "fixture";
11411
+ var inHookSubtree = (flat, f) => isTeardown(f.category) || f.ancestors.some((a) => isTeardown(flat[a].category));
11411
11412
  function flatten(steps) {
11412
11413
  const out = [];
11413
11414
  const walk = (nodes, depth, ancestors) => {
@@ -11426,7 +11427,7 @@ function flatten(steps) {
11426
11427
  walk(steps ?? [], 0, []);
11427
11428
  return out;
11428
11429
  }
11429
- function materialize(n) {
11430
+ function materialize(n, isFailing) {
11430
11431
  const loc = n.raw.location;
11431
11432
  return {
11432
11433
  title: clampWithEllipsis(n.raw.title ?? "", MAX_TITLE_CHARS),
@@ -11434,17 +11435,16 @@ function materialize(n) {
11434
11435
  durationMs: Math.max(0, Math.round(n.raw.duration ?? 0)),
11435
11436
  depth: n.depth,
11436
11437
  failed: n.hasError,
11437
- errorMessage: n.raw.error?.message ? clampWithEllipsis(stripAnsi(n.raw.error.message).split("\n")[0], MAX_TITLE_CHARS) : void 0,
11438
+ errorMessage: isFailing && n.raw.error?.message ? clampWithEllipsis(stripAnsi(n.raw.error.message).split("\n")[0], MAX_TITLE_CHARS) : void 0,
11438
11439
  location: loc?.file != null ? `${loc.file}:${loc.line ?? 0}` : void 0
11439
11440
  };
11440
11441
  }
11441
11442
  function pickFailingIdx(flat) {
11442
- const inTeardown = (f) => isTeardown(f.category) || f.ancestors.some((a) => isTeardown(flat[a].category));
11443
11443
  const pick = (allowTeardown) => {
11444
11444
  let idx = -1;
11445
11445
  for (let i = 0; i < flat.length; i++) {
11446
11446
  const f = flat[i];
11447
- if (!f.hasError || !allowTeardown && inTeardown(f)) continue;
11447
+ if (!f.hasError || !allowTeardown && inHookSubtree(flat, f)) continue;
11448
11448
  if (idx === -1 || f.depth >= flat[idx].depth) idx = i;
11449
11449
  }
11450
11450
  return idx;
@@ -11467,7 +11467,7 @@ function compactSteps(rawSteps, opts = {}) {
11467
11467
  if (opts.apiSteps) {
11468
11468
  const pwApiBefore = [];
11469
11469
  for (let i = 0; i < failingIdx; i++) {
11470
- if (flat[i].category === "pw:api") pwApiBefore.push(i);
11470
+ if (flat[i].category === "pw:api" && !inHookSubtree(flat, flat[i])) pwApiBefore.push(i);
11471
11471
  }
11472
11472
  for (const i of pwApiBefore.slice(-PW_API_WINDOW)) keep.add(i);
11473
11473
  }
@@ -11483,7 +11483,7 @@ function compactSteps(rawSteps, opts = {}) {
11483
11483
  const mat = (i) => {
11484
11484
  let es = cache.get(i);
11485
11485
  if (!es) {
11486
- es = materialize(flat[i]);
11486
+ es = materialize(flat[i], i === failingIdx);
11487
11487
  cache.set(i, es);
11488
11488
  }
11489
11489
  return es;
@@ -13496,7 +13496,7 @@ var PdfReporter = class {
13496
13496
  this.pdfGenerator = new PdfGenerator();
13497
13497
  this.options = parseOptions(rawOptions);
13498
13498
  this.dataCollector = new DataCollector(this.options.capture);
13499
- const version = true ? "0.8.0" : "0.x";
13499
+ const version = true ? "0.8.1" : "0.x";
13500
13500
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
13501
13501
  this.licenseClient = new LicenseClient({
13502
13502
  licenseKey: this.options.licenseKey,
@@ -11,8 +11,8 @@
11
11
  <th style="width:72px;text-align:right;">Duration</th>
12
12
  </tr>
13
13
  </thead>
14
- <tbody>
15
14
  {{#each failures}}
15
+ <tbody class="defect-row-group">
16
16
  <tr class="no-break">
17
17
  <td class="defect-id">DEF-{{addOne @index}}</td>
18
18
  <td style="font-weight:500;">{{testTitle}}</td>
@@ -24,9 +24,14 @@
24
24
  <tr class="defect-detail-row">
25
25
  <td colspan="5">
26
26
  <div class="defect-detail">
27
+ {{! Error sits at the failing step inside the trail. Only when there is no
28
+ identified failing step (capture.steps off, or no errored step) is it
29
+ shown here under the title as a fallback. }}
30
+ {{#unless execution.failingStep}}
27
31
  {{#if error.message}}
28
32
  <div class="defect-detail__error">{{error.message}}</div>
29
33
  {{/if}}
34
+ {{/unless}}
30
35
 
31
36
  {{#if execution.steps.length}}
32
37
  <div class="repro">
@@ -38,6 +43,7 @@
38
43
  <span class="repro-step__title">{{title}}</span>
39
44
  {{#if failed}}{{#if location}}<span class="repro-step__loc">{{location}}</span>{{/if}}{{/if}}
40
45
  <span class="repro-step__dur">{{formatDuration durationMs}}</span>
46
+ {{#if errorMessage}}<div class="repro-step__error">{{../error.message}}</div>{{/if}}
41
47
  </li>
42
48
  {{/each}}
43
49
  </ol>
@@ -64,8 +70,8 @@
64
70
  </td>
65
71
  </tr>
66
72
  {{/if}}
73
+ </tbody>
67
74
  {{/each}}
68
- </tbody>
69
75
  </table>
70
76
  </div>
71
77
  {{/if}}
@@ -409,6 +409,9 @@ code {
409
409
  design-system-consistent: a light bordered card (NO side rail, per the
410
410
  side-stripe ban in DESIGN.md), with hairline dividers between sub-blocks —
411
411
  mirroring the reworked .failure-detail and .retry-history patterns. */
412
+ /* Each failure is its own <tbody> so its summary row and detail card stay together
413
+ across a page break — the detail no longer strands under a repeated header. */
414
+ .defect-row-group { break-inside: avoid; page-break-inside: avoid; }
412
415
  .defect-detail-row td { padding: 0; border-top: none; }
413
416
  .defect-detail {
414
417
  border: 1px solid var(--border-2);
@@ -435,12 +438,19 @@ code {
435
438
  .repro { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--border); }
436
439
  .repro-steps { list-style: none; margin: 6px 0 0; padding: 0; }
437
440
  .repro-step {
438
- display: flex; align-items: baseline; gap: 8px;
441
+ display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
439
442
  /* indent by tree depth (capped) — the value comes from `--d` on the element */
440
443
  padding: 2px 0 2px min(60px, calc(var(--d, 0) * 12px));
441
444
  font-size: 9px; line-height: 1.5;
442
445
  page-break-inside: avoid; break-inside: avoid;
443
446
  }
447
+ /* The error sits at the failing step — full width below its row, indented past the
448
+ category column so it reads as belonging to that step. */
449
+ .repro-step__error {
450
+ flex-basis: 100%; margin: 3px 0 2px; padding-left: 60px;
451
+ font-family: 'JetBrains Mono', monospace; font-size: 9px; line-height: 1.55;
452
+ color: #8A2E1E; white-space: pre-wrap; word-break: break-word;
453
+ }
444
454
  .repro-step__cat {
445
455
  flex: none; min-width: 52px;
446
456
  font-family: 'JetBrains Mono', monospace; font-size: 8px; color: var(--text-3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reportforge/playwright-pdf",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Enterprise-ready PDF reports for Playwright Test — minimal, detailed, and executive templates with CI/CD integrations",
5
5
  "license": "MIT",
6
6
  "author": "ReportForge",