@reportforge/playwright-pdf 0.24.0 → 0.25.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/README.md +3 -3
- package/ci-templates/azure-devops/azure-pipelines.yml +58 -58
- package/ci-templates/github-actions/basic-workflow.yml +44 -44
- package/ci-templates/gitlab-ci/.gitlab-ci.yml +42 -42
- package/ci-templates/jenkins/Jenkinsfile +54 -54
- package/dist/cli/index.js +0 -0
- package/dist/index.js +230 -9
- package/dist/index.mjs +230 -9
- package/dist/templates/layouts/detailed.hbs +41 -41
- package/dist/templates/layouts/executive.hbs +41 -41
- package/dist/templates/layouts/minimal.hbs +41 -41
- package/dist/templates/partials/analysis-oneliner.hbs +3 -3
- package/dist/templates/partials/chart-summary-grid.hbs +40 -40
- package/dist/templates/partials/chart-trend-card.hbs +16 -16
- package/dist/templates/partials/charts.hbs +433 -433
- package/dist/templates/partials/ci-environment.hbs +56 -56
- package/dist/templates/partials/cover-page.hbs +48 -48
- package/dist/templates/partials/defect-log.hbs +79 -79
- package/dist/templates/partials/executive-hero-summary.hbs +66 -66
- package/dist/templates/partials/executive-summary.hbs +78 -78
- package/dist/templates/partials/failure-card.hbs +35 -35
- package/dist/templates/partials/head.hbs +17 -17
- package/dist/templates/partials/release-gate.hbs +84 -84
- package/dist/templates/partials/requirements-matrix.hbs +51 -51
- package/dist/templates/partials/suite-breakdown.hbs +89 -89
- package/dist/templates/partials/watermark.hbs +42 -42
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -14230,11 +14230,35 @@ var NETWORK_TIMEOUT_MS3 = 8e3;
|
|
|
14230
14230
|
var LIVE_DRAIN_DEADLINE_MS = 1e4;
|
|
14231
14231
|
var MAX_FLUSH_MS = 3e4;
|
|
14232
14232
|
var WARN_THROTTLE_MS = 3e4;
|
|
14233
|
+
var TREE_UPDATE_WEIGHT = 25;
|
|
14234
|
+
function redactTestUpdate(update, redact) {
|
|
14235
|
+
if (update.title !== void 0) update.title = redact(update.title);
|
|
14236
|
+
if (update.currentStep !== void 0) update.currentStep = redact(update.currentStep);
|
|
14237
|
+
if (update.file !== void 0) update.file = redact(update.file);
|
|
14238
|
+
if (update.suitePath) update.suitePath = update.suitePath.map(redact);
|
|
14239
|
+
if (update.projectName !== void 0) update.projectName = redact(update.projectName);
|
|
14240
|
+
if (update.steps) redactStepNodes(update.steps, redact);
|
|
14241
|
+
if (update.errors) update.errors.forEach((e) => redactError(e, redact));
|
|
14242
|
+
}
|
|
14243
|
+
function redactStepNodes(nodes, redact) {
|
|
14244
|
+
for (const node of nodes) {
|
|
14245
|
+
node.t = redact(node.t);
|
|
14246
|
+
if (node.err) redactError(node.err, redact);
|
|
14247
|
+
if (node.ch) redactStepNodes(node.ch, redact);
|
|
14248
|
+
}
|
|
14249
|
+
}
|
|
14250
|
+
function redactError(err, redact) {
|
|
14251
|
+
err.message = redact(err.message);
|
|
14252
|
+
if (err.stack !== void 0) err.stack = redact(err.stack);
|
|
14253
|
+
if (err.snippet !== void 0) err.snippet = redact(err.snippet);
|
|
14254
|
+
}
|
|
14233
14255
|
var LiveStreamer = class {
|
|
14234
14256
|
constructor(config) {
|
|
14235
14257
|
this.config = config;
|
|
14236
14258
|
this.testBuffer = [];
|
|
14237
14259
|
this.eventBuffer = [];
|
|
14260
|
+
/** Weighted size of the buffers (tree-bearing updates count TREE_UPDATE_WEIGHT). */
|
|
14261
|
+
this.bufferWeight = 0;
|
|
14238
14262
|
/** Monotonic per-shard event counter → stable `{shardKey}:{seq}` event ids. */
|
|
14239
14263
|
this.eventSeq = 0;
|
|
14240
14264
|
/**
|
|
@@ -14260,13 +14284,11 @@ var LiveStreamer = class {
|
|
|
14260
14284
|
}
|
|
14261
14285
|
enqueueTest(update) {
|
|
14262
14286
|
if (!this.enabled) return;
|
|
14263
|
-
if (this.config.redact)
|
|
14264
|
-
if (update.title !== void 0) update.title = this.config.redact(update.title);
|
|
14265
|
-
if (update.currentStep !== void 0) update.currentStep = this.config.redact(update.currentStep);
|
|
14266
|
-
}
|
|
14287
|
+
if (this.config.redact) redactTestUpdate(update, this.config.redact);
|
|
14267
14288
|
const prevRetry = this.testStates.get(update.id)?.retry;
|
|
14268
14289
|
this.testStates.set(update.id, { status: update.status, retry: update.retry ?? prevRetry });
|
|
14269
14290
|
this.testBuffer.push(update);
|
|
14291
|
+
this.bufferWeight += update.steps ? TREE_UPDATE_WEIGHT : 1;
|
|
14270
14292
|
this.capPreLicenseBuffer();
|
|
14271
14293
|
this.scheduleFlush();
|
|
14272
14294
|
}
|
|
@@ -14278,6 +14300,7 @@ var LiveStreamer = class {
|
|
|
14278
14300
|
}
|
|
14279
14301
|
event.id = `${this.config.shardKey}:${this.eventSeq++}`;
|
|
14280
14302
|
this.eventBuffer.push(event);
|
|
14303
|
+
this.bufferWeight += 1;
|
|
14281
14304
|
this.capPreLicenseBuffer();
|
|
14282
14305
|
this.scheduleFlush();
|
|
14283
14306
|
}
|
|
@@ -14395,9 +14418,13 @@ var LiveStreamer = class {
|
|
|
14395
14418
|
if (dropEvents > 0) this.eventBuffer.splice(0, dropEvents);
|
|
14396
14419
|
overflow -= dropEvents;
|
|
14397
14420
|
if (overflow > 0) this.testBuffer.splice(0, overflow);
|
|
14421
|
+
this.bufferWeight = this.currentBufferWeight();
|
|
14422
|
+
}
|
|
14423
|
+
currentBufferWeight() {
|
|
14424
|
+
return this.eventBuffer.length + this.testBuffer.reduce((w, t) => w + (t.steps ? TREE_UPDATE_WEIGHT : 1), 0);
|
|
14398
14425
|
}
|
|
14399
14426
|
scheduleFlush() {
|
|
14400
|
-
if (this.
|
|
14427
|
+
if (this.bufferWeight >= this.flushMaxBatch) {
|
|
14401
14428
|
void this.flush("running");
|
|
14402
14429
|
return;
|
|
14403
14430
|
}
|
|
@@ -14418,6 +14445,7 @@ var LiveStreamer = class {
|
|
|
14418
14445
|
if (status !== "finished" && tests.length === 0 && events.length === 0) return;
|
|
14419
14446
|
this.testBuffer = [];
|
|
14420
14447
|
this.eventBuffer = [];
|
|
14448
|
+
this.bufferWeight = 0;
|
|
14421
14449
|
const body = {
|
|
14422
14450
|
runId: this.config.runId,
|
|
14423
14451
|
shardKey: this.config.shardKey,
|
|
@@ -14452,6 +14480,7 @@ var LiveStreamer = class {
|
|
|
14452
14480
|
this.enabled = false;
|
|
14453
14481
|
this.testBuffer = [];
|
|
14454
14482
|
this.eventBuffer = [];
|
|
14483
|
+
this.bufferWeight = 0;
|
|
14455
14484
|
logger.debug("Live streaming disabled \u2014 no active license or live entitlement.");
|
|
14456
14485
|
return false;
|
|
14457
14486
|
}
|
|
@@ -14538,25 +14567,213 @@ function shardKeyOf(shard) {
|
|
|
14538
14567
|
|
|
14539
14568
|
// src/live/event-map.ts
|
|
14540
14569
|
init_cjs_shims();
|
|
14570
|
+
|
|
14571
|
+
// src/live/step-tree.ts
|
|
14572
|
+
init_cjs_shims();
|
|
14573
|
+
var MAX_TREE_DEPTH = 10;
|
|
14574
|
+
var MAX_TREE_NODES = 150;
|
|
14575
|
+
var MAX_TREE_JSON = 24e3;
|
|
14576
|
+
var MAX_NODE_TITLE = 200;
|
|
14577
|
+
var MAX_ERRORS = 3;
|
|
14578
|
+
var MAX_ERR_MESSAGE = 2e3;
|
|
14579
|
+
var MAX_ERR_STACK = 8192;
|
|
14580
|
+
var MAX_ERR_SNIPPET = 4096;
|
|
14581
|
+
var isHook = (c) => c === "hook" || c === "fixture";
|
|
14582
|
+
function flatten2(steps) {
|
|
14583
|
+
const out = [];
|
|
14584
|
+
const walk = (nodes, depth, ancestors) => {
|
|
14585
|
+
for (const s of nodes ?? []) {
|
|
14586
|
+
const index = out.length;
|
|
14587
|
+
out.push({
|
|
14588
|
+
raw: s,
|
|
14589
|
+
category: s.category ?? "",
|
|
14590
|
+
depth,
|
|
14591
|
+
hasError: s.error != null,
|
|
14592
|
+
ancestors
|
|
14593
|
+
});
|
|
14594
|
+
if (s.steps?.length) walk(s.steps, depth + 1, [...ancestors, index]);
|
|
14595
|
+
}
|
|
14596
|
+
};
|
|
14597
|
+
walk(steps ?? [], 0, []);
|
|
14598
|
+
return out;
|
|
14599
|
+
}
|
|
14600
|
+
var inHookSubtree2 = (flat, f) => isHook(f.category) || f.ancestors.some((a) => isHook(flat[a].category));
|
|
14601
|
+
function pickFailingIdx2(flat) {
|
|
14602
|
+
const pick = (allowTeardown) => {
|
|
14603
|
+
let idx = -1;
|
|
14604
|
+
for (let i = 0; i < flat.length; i++) {
|
|
14605
|
+
const f = flat[i];
|
|
14606
|
+
if (!f.hasError || !allowTeardown && inHookSubtree2(flat, f)) continue;
|
|
14607
|
+
if (idx === -1 || f.depth >= flat[idx].depth) idx = i;
|
|
14608
|
+
}
|
|
14609
|
+
return idx;
|
|
14610
|
+
};
|
|
14611
|
+
const body = pick(false);
|
|
14612
|
+
return body >= 0 ? body : pick(true);
|
|
14613
|
+
}
|
|
14614
|
+
function mapCategory(category) {
|
|
14615
|
+
if (category === "test.step") return "step";
|
|
14616
|
+
if (category === "expect") return "expect";
|
|
14617
|
+
if (isHook(category)) return "hook";
|
|
14618
|
+
return "api";
|
|
14619
|
+
}
|
|
14620
|
+
function buildErrorInfo(err) {
|
|
14621
|
+
if (!err?.message) return void 0;
|
|
14622
|
+
return {
|
|
14623
|
+
message: clampWithEllipsis(stripAnsi(err.message), MAX_ERR_MESSAGE),
|
|
14624
|
+
...err.stack ? { stack: clampWithEllipsis(stripAnsi(err.stack), MAX_ERR_STACK) } : {},
|
|
14625
|
+
...err.snippet ? { snippet: clampWithEllipsis(stripAnsi(err.snippet), MAX_ERR_SNIPPET) } : {}
|
|
14626
|
+
};
|
|
14627
|
+
}
|
|
14628
|
+
function mapResultErrors(errors) {
|
|
14629
|
+
if (!errors?.length) return void 0;
|
|
14630
|
+
const out = errors.slice(0, MAX_ERRORS).map((e) => buildErrorInfo(e)).filter((e) => e !== void 0);
|
|
14631
|
+
return out.length > 0 ? out : void 0;
|
|
14632
|
+
}
|
|
14633
|
+
function buildLiveStepTree(steps) {
|
|
14634
|
+
if (!steps?.length) return void 0;
|
|
14635
|
+
const flat = flatten2(steps);
|
|
14636
|
+
if (flat.length === 0) return void 0;
|
|
14637
|
+
const failingIdx = pickFailingIdx2(flat);
|
|
14638
|
+
const failingPath = /* @__PURE__ */ new Set();
|
|
14639
|
+
if (failingIdx >= 0) {
|
|
14640
|
+
failingPath.add(flat[failingIdx].raw);
|
|
14641
|
+
for (const a of flat[failingIdx].ancestors) failingPath.add(flat[a].raw);
|
|
14642
|
+
}
|
|
14643
|
+
const failingLeaf = failingIdx >= 0 ? flat[failingIdx].raw : void 0;
|
|
14644
|
+
for (const level of [0, 1, 2, 3]) {
|
|
14645
|
+
const budget = { left: MAX_TREE_NODES };
|
|
14646
|
+
const tree = buildChildren(steps, {
|
|
14647
|
+
level,
|
|
14648
|
+
depth: 0,
|
|
14649
|
+
underHook: false,
|
|
14650
|
+
underApiOrExpect: false,
|
|
14651
|
+
failingPath,
|
|
14652
|
+
failingLeaf,
|
|
14653
|
+
budget
|
|
14654
|
+
});
|
|
14655
|
+
if (tree.length === 0) return void 0;
|
|
14656
|
+
if (budget.left >= 0 && JSON.stringify(tree).length <= MAX_TREE_JSON) return tree;
|
|
14657
|
+
}
|
|
14658
|
+
return void 0;
|
|
14659
|
+
}
|
|
14660
|
+
function keepNode(s, ctx) {
|
|
14661
|
+
if (ctx.failingPath.has(s)) return true;
|
|
14662
|
+
if (!s.title) return false;
|
|
14663
|
+
const c = s.category ?? "";
|
|
14664
|
+
if (isHook(c) || ctx.underHook) return false;
|
|
14665
|
+
if (c === "test.step") return true;
|
|
14666
|
+
if (ctx.underApiOrExpect) return false;
|
|
14667
|
+
if (c === "expect") return ctx.level < 2;
|
|
14668
|
+
if (c === "pw:api") return ctx.level < 1;
|
|
14669
|
+
return false;
|
|
14670
|
+
}
|
|
14671
|
+
function containsFailing(s, leaf) {
|
|
14672
|
+
if (!leaf) return false;
|
|
14673
|
+
if (s === leaf) return true;
|
|
14674
|
+
return (s.steps ?? []).some((child) => containsFailing(child, leaf));
|
|
14675
|
+
}
|
|
14676
|
+
function buildChildren(nodes, ctx) {
|
|
14677
|
+
const out = [];
|
|
14678
|
+
for (const s of nodes ?? []) {
|
|
14679
|
+
if (ctx.budget.left <= 0) {
|
|
14680
|
+
ctx.budget.left = -1;
|
|
14681
|
+
break;
|
|
14682
|
+
}
|
|
14683
|
+
const c = s.category ?? "";
|
|
14684
|
+
if (!keepNode(s, ctx)) {
|
|
14685
|
+
if (containsFailing(s, ctx.failingLeaf)) {
|
|
14686
|
+
out.push(
|
|
14687
|
+
...buildChildren(s.steps, {
|
|
14688
|
+
...ctx,
|
|
14689
|
+
underHook: ctx.underHook || isHook(c),
|
|
14690
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14691
|
+
})
|
|
14692
|
+
);
|
|
14693
|
+
}
|
|
14694
|
+
continue;
|
|
14695
|
+
}
|
|
14696
|
+
ctx.budget.left -= 1;
|
|
14697
|
+
const node = {
|
|
14698
|
+
t: clampWithEllipsis(stripAnsi(s.title ?? ""), MAX_NODE_TITLE),
|
|
14699
|
+
c: mapCategory(c),
|
|
14700
|
+
...Number.isFinite(s.duration) && s.duration >= 0 ? { d: Math.round(s.duration) } : {},
|
|
14701
|
+
...s.error != null ? { s: "failed" } : {},
|
|
14702
|
+
...s === ctx.failingLeaf ? errField(s) : {}
|
|
14703
|
+
};
|
|
14704
|
+
if (s.steps?.length) {
|
|
14705
|
+
if (ctx.depth + 1 >= MAX_TREE_DEPTH) {
|
|
14706
|
+
if (!node.err && containsFailing(s, ctx.failingLeaf) && s !== ctx.failingLeaf) {
|
|
14707
|
+
const hoisted = errField(ctx.failingLeaf);
|
|
14708
|
+
if (hoisted.err) {
|
|
14709
|
+
node.err = hoisted.err;
|
|
14710
|
+
node.s = "failed";
|
|
14711
|
+
}
|
|
14712
|
+
}
|
|
14713
|
+
} else {
|
|
14714
|
+
const ch = buildChildren(s.steps, {
|
|
14715
|
+
...ctx,
|
|
14716
|
+
depth: ctx.depth + 1,
|
|
14717
|
+
underHook: ctx.underHook || isHook(c),
|
|
14718
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14719
|
+
});
|
|
14720
|
+
if (ch.length > 0) node.ch = ch;
|
|
14721
|
+
}
|
|
14722
|
+
}
|
|
14723
|
+
out.push(node);
|
|
14724
|
+
}
|
|
14725
|
+
return out;
|
|
14726
|
+
}
|
|
14727
|
+
function errField(s) {
|
|
14728
|
+
const err = buildErrorInfo(s.error);
|
|
14729
|
+
return err ? { err } : {};
|
|
14730
|
+
}
|
|
14731
|
+
|
|
14732
|
+
// src/live/event-map.ts
|
|
14541
14733
|
var MAX_TITLE_CHARS2 = 200;
|
|
14542
14734
|
var MAX_STEP_TITLE_CHARS = 200;
|
|
14543
14735
|
var MAX_CONSOLE_CHARS = 2048;
|
|
14736
|
+
var MAX_FILE_CHARS = 300;
|
|
14737
|
+
var MAX_PROJECT_CHARS = 100;
|
|
14738
|
+
var MAX_SUITE_SEGMENTS = 10;
|
|
14739
|
+
function suiteIdentity(test) {
|
|
14740
|
+
const path15 = typeof test.titlePath === "function" ? test.titlePath() : [];
|
|
14741
|
+
if (!Array.isArray(path15) || path15.length < 4) return {};
|
|
14742
|
+
const [, project, file, ...rest] = path15;
|
|
14743
|
+
const describes = rest.slice(0, -1);
|
|
14744
|
+
const out = {};
|
|
14745
|
+
if (typeof file === "string" && file) {
|
|
14746
|
+
out.file = clampWithEllipsis(file.replace(/\\/g, "/"), MAX_FILE_CHARS);
|
|
14747
|
+
}
|
|
14748
|
+
const segs = describes.filter((s) => typeof s === "string" && s.length > 0).slice(0, MAX_SUITE_SEGMENTS).map((s) => clampWithEllipsis(s, MAX_STEP_TITLE_CHARS));
|
|
14749
|
+
if (segs.length > 0) out.suitePath = segs;
|
|
14750
|
+
if (typeof project === "string" && project) {
|
|
14751
|
+
out.projectName = clampWithEllipsis(project, MAX_PROJECT_CHARS);
|
|
14752
|
+
}
|
|
14753
|
+
return out;
|
|
14754
|
+
}
|
|
14544
14755
|
function mapTestBegin(test, shardKey) {
|
|
14545
14756
|
return {
|
|
14546
14757
|
id: test.id,
|
|
14547
14758
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14548
14759
|
status: "running",
|
|
14549
|
-
shardKey
|
|
14760
|
+
shardKey,
|
|
14761
|
+
...suiteIdentity(test)
|
|
14550
14762
|
};
|
|
14551
14763
|
}
|
|
14552
14764
|
function mapTestEnd(test, result, shardKey) {
|
|
14765
|
+
const steps = buildLiveStepTree(result.steps);
|
|
14766
|
+
const errors = mapResultErrors(result.errors);
|
|
14553
14767
|
return {
|
|
14554
14768
|
id: test.id,
|
|
14555
14769
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14556
14770
|
status: result.status,
|
|
14557
14771
|
durationMs: result.duration,
|
|
14558
14772
|
retry: result.retry,
|
|
14559
|
-
shardKey
|
|
14773
|
+
shardKey,
|
|
14774
|
+
...suiteIdentity(test),
|
|
14775
|
+
...steps ? { steps } : {},
|
|
14776
|
+
...errors ? { errors } : {}
|
|
14560
14777
|
};
|
|
14561
14778
|
}
|
|
14562
14779
|
function isLifecycleStep(step) {
|
|
@@ -14606,7 +14823,11 @@ function mapStep(testId, step, phase, shardKey) {
|
|
|
14606
14823
|
startedAt: step.startTime?.getTime(),
|
|
14607
14824
|
// Nesting depth (test.step ancestors) so the page indents children under their
|
|
14608
14825
|
// parent step. 0 → flat, so a test without test.steps renders as a flat trail.
|
|
14609
|
-
depth: testStepDepth2(step)
|
|
14826
|
+
depth: testStepDepth2(step),
|
|
14827
|
+
// Category so the trail styles pw:api actions and expects distinctly. Hooks
|
|
14828
|
+
// and fixtures never reach here non-errored (shouldEmitStep drops them); an
|
|
14829
|
+
// errored one maps to 'api' — the trail has no dedicated hook style.
|
|
14830
|
+
category: step.category === "test.step" ? "step" : step.category === "expect" ? "expect" : "api"
|
|
14610
14831
|
};
|
|
14611
14832
|
}
|
|
14612
14833
|
function mapConsole(kind, chunk, testId, shardKey) {
|
|
@@ -14638,7 +14859,7 @@ var PdfReporter = class {
|
|
|
14638
14859
|
if (this.options.redact.enabled) {
|
|
14639
14860
|
this.redactor = new Redactor(this.options.redact);
|
|
14640
14861
|
}
|
|
14641
|
-
this.version = true ? "0.
|
|
14862
|
+
this.version = true ? "0.25.0" : "0.x";
|
|
14642
14863
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14643
14864
|
this.licenseClient = new LicenseClient({
|
|
14644
14865
|
licenseKey: this.options.licenseKey,
|
package/dist/index.mjs
CHANGED
|
@@ -14231,11 +14231,35 @@ var NETWORK_TIMEOUT_MS3 = 8e3;
|
|
|
14231
14231
|
var LIVE_DRAIN_DEADLINE_MS = 1e4;
|
|
14232
14232
|
var MAX_FLUSH_MS = 3e4;
|
|
14233
14233
|
var WARN_THROTTLE_MS = 3e4;
|
|
14234
|
+
var TREE_UPDATE_WEIGHT = 25;
|
|
14235
|
+
function redactTestUpdate(update, redact) {
|
|
14236
|
+
if (update.title !== void 0) update.title = redact(update.title);
|
|
14237
|
+
if (update.currentStep !== void 0) update.currentStep = redact(update.currentStep);
|
|
14238
|
+
if (update.file !== void 0) update.file = redact(update.file);
|
|
14239
|
+
if (update.suitePath) update.suitePath = update.suitePath.map(redact);
|
|
14240
|
+
if (update.projectName !== void 0) update.projectName = redact(update.projectName);
|
|
14241
|
+
if (update.steps) redactStepNodes(update.steps, redact);
|
|
14242
|
+
if (update.errors) update.errors.forEach((e) => redactError(e, redact));
|
|
14243
|
+
}
|
|
14244
|
+
function redactStepNodes(nodes, redact) {
|
|
14245
|
+
for (const node of nodes) {
|
|
14246
|
+
node.t = redact(node.t);
|
|
14247
|
+
if (node.err) redactError(node.err, redact);
|
|
14248
|
+
if (node.ch) redactStepNodes(node.ch, redact);
|
|
14249
|
+
}
|
|
14250
|
+
}
|
|
14251
|
+
function redactError(err, redact) {
|
|
14252
|
+
err.message = redact(err.message);
|
|
14253
|
+
if (err.stack !== void 0) err.stack = redact(err.stack);
|
|
14254
|
+
if (err.snippet !== void 0) err.snippet = redact(err.snippet);
|
|
14255
|
+
}
|
|
14234
14256
|
var LiveStreamer = class {
|
|
14235
14257
|
constructor(config) {
|
|
14236
14258
|
this.config = config;
|
|
14237
14259
|
this.testBuffer = [];
|
|
14238
14260
|
this.eventBuffer = [];
|
|
14261
|
+
/** Weighted size of the buffers (tree-bearing updates count TREE_UPDATE_WEIGHT). */
|
|
14262
|
+
this.bufferWeight = 0;
|
|
14239
14263
|
/** Monotonic per-shard event counter → stable `{shardKey}:{seq}` event ids. */
|
|
14240
14264
|
this.eventSeq = 0;
|
|
14241
14265
|
/**
|
|
@@ -14261,13 +14285,11 @@ var LiveStreamer = class {
|
|
|
14261
14285
|
}
|
|
14262
14286
|
enqueueTest(update) {
|
|
14263
14287
|
if (!this.enabled) return;
|
|
14264
|
-
if (this.config.redact)
|
|
14265
|
-
if (update.title !== void 0) update.title = this.config.redact(update.title);
|
|
14266
|
-
if (update.currentStep !== void 0) update.currentStep = this.config.redact(update.currentStep);
|
|
14267
|
-
}
|
|
14288
|
+
if (this.config.redact) redactTestUpdate(update, this.config.redact);
|
|
14268
14289
|
const prevRetry = this.testStates.get(update.id)?.retry;
|
|
14269
14290
|
this.testStates.set(update.id, { status: update.status, retry: update.retry ?? prevRetry });
|
|
14270
14291
|
this.testBuffer.push(update);
|
|
14292
|
+
this.bufferWeight += update.steps ? TREE_UPDATE_WEIGHT : 1;
|
|
14271
14293
|
this.capPreLicenseBuffer();
|
|
14272
14294
|
this.scheduleFlush();
|
|
14273
14295
|
}
|
|
@@ -14279,6 +14301,7 @@ var LiveStreamer = class {
|
|
|
14279
14301
|
}
|
|
14280
14302
|
event.id = `${this.config.shardKey}:${this.eventSeq++}`;
|
|
14281
14303
|
this.eventBuffer.push(event);
|
|
14304
|
+
this.bufferWeight += 1;
|
|
14282
14305
|
this.capPreLicenseBuffer();
|
|
14283
14306
|
this.scheduleFlush();
|
|
14284
14307
|
}
|
|
@@ -14396,9 +14419,13 @@ var LiveStreamer = class {
|
|
|
14396
14419
|
if (dropEvents > 0) this.eventBuffer.splice(0, dropEvents);
|
|
14397
14420
|
overflow -= dropEvents;
|
|
14398
14421
|
if (overflow > 0) this.testBuffer.splice(0, overflow);
|
|
14422
|
+
this.bufferWeight = this.currentBufferWeight();
|
|
14423
|
+
}
|
|
14424
|
+
currentBufferWeight() {
|
|
14425
|
+
return this.eventBuffer.length + this.testBuffer.reduce((w, t) => w + (t.steps ? TREE_UPDATE_WEIGHT : 1), 0);
|
|
14399
14426
|
}
|
|
14400
14427
|
scheduleFlush() {
|
|
14401
|
-
if (this.
|
|
14428
|
+
if (this.bufferWeight >= this.flushMaxBatch) {
|
|
14402
14429
|
void this.flush("running");
|
|
14403
14430
|
return;
|
|
14404
14431
|
}
|
|
@@ -14419,6 +14446,7 @@ var LiveStreamer = class {
|
|
|
14419
14446
|
if (status !== "finished" && tests.length === 0 && events.length === 0) return;
|
|
14420
14447
|
this.testBuffer = [];
|
|
14421
14448
|
this.eventBuffer = [];
|
|
14449
|
+
this.bufferWeight = 0;
|
|
14422
14450
|
const body = {
|
|
14423
14451
|
runId: this.config.runId,
|
|
14424
14452
|
shardKey: this.config.shardKey,
|
|
@@ -14453,6 +14481,7 @@ var LiveStreamer = class {
|
|
|
14453
14481
|
this.enabled = false;
|
|
14454
14482
|
this.testBuffer = [];
|
|
14455
14483
|
this.eventBuffer = [];
|
|
14484
|
+
this.bufferWeight = 0;
|
|
14456
14485
|
logger.debug("Live streaming disabled \u2014 no active license or live entitlement.");
|
|
14457
14486
|
return false;
|
|
14458
14487
|
}
|
|
@@ -14539,25 +14568,213 @@ function shardKeyOf(shard) {
|
|
|
14539
14568
|
|
|
14540
14569
|
// src/live/event-map.ts
|
|
14541
14570
|
init_esm_shims();
|
|
14571
|
+
|
|
14572
|
+
// src/live/step-tree.ts
|
|
14573
|
+
init_esm_shims();
|
|
14574
|
+
var MAX_TREE_DEPTH = 10;
|
|
14575
|
+
var MAX_TREE_NODES = 150;
|
|
14576
|
+
var MAX_TREE_JSON = 24e3;
|
|
14577
|
+
var MAX_NODE_TITLE = 200;
|
|
14578
|
+
var MAX_ERRORS = 3;
|
|
14579
|
+
var MAX_ERR_MESSAGE = 2e3;
|
|
14580
|
+
var MAX_ERR_STACK = 8192;
|
|
14581
|
+
var MAX_ERR_SNIPPET = 4096;
|
|
14582
|
+
var isHook = (c) => c === "hook" || c === "fixture";
|
|
14583
|
+
function flatten2(steps) {
|
|
14584
|
+
const out = [];
|
|
14585
|
+
const walk = (nodes, depth, ancestors) => {
|
|
14586
|
+
for (const s of nodes ?? []) {
|
|
14587
|
+
const index = out.length;
|
|
14588
|
+
out.push({
|
|
14589
|
+
raw: s,
|
|
14590
|
+
category: s.category ?? "",
|
|
14591
|
+
depth,
|
|
14592
|
+
hasError: s.error != null,
|
|
14593
|
+
ancestors
|
|
14594
|
+
});
|
|
14595
|
+
if (s.steps?.length) walk(s.steps, depth + 1, [...ancestors, index]);
|
|
14596
|
+
}
|
|
14597
|
+
};
|
|
14598
|
+
walk(steps ?? [], 0, []);
|
|
14599
|
+
return out;
|
|
14600
|
+
}
|
|
14601
|
+
var inHookSubtree2 = (flat, f) => isHook(f.category) || f.ancestors.some((a) => isHook(flat[a].category));
|
|
14602
|
+
function pickFailingIdx2(flat) {
|
|
14603
|
+
const pick = (allowTeardown) => {
|
|
14604
|
+
let idx = -1;
|
|
14605
|
+
for (let i = 0; i < flat.length; i++) {
|
|
14606
|
+
const f = flat[i];
|
|
14607
|
+
if (!f.hasError || !allowTeardown && inHookSubtree2(flat, f)) continue;
|
|
14608
|
+
if (idx === -1 || f.depth >= flat[idx].depth) idx = i;
|
|
14609
|
+
}
|
|
14610
|
+
return idx;
|
|
14611
|
+
};
|
|
14612
|
+
const body = pick(false);
|
|
14613
|
+
return body >= 0 ? body : pick(true);
|
|
14614
|
+
}
|
|
14615
|
+
function mapCategory(category) {
|
|
14616
|
+
if (category === "test.step") return "step";
|
|
14617
|
+
if (category === "expect") return "expect";
|
|
14618
|
+
if (isHook(category)) return "hook";
|
|
14619
|
+
return "api";
|
|
14620
|
+
}
|
|
14621
|
+
function buildErrorInfo(err) {
|
|
14622
|
+
if (!err?.message) return void 0;
|
|
14623
|
+
return {
|
|
14624
|
+
message: clampWithEllipsis(stripAnsi(err.message), MAX_ERR_MESSAGE),
|
|
14625
|
+
...err.stack ? { stack: clampWithEllipsis(stripAnsi(err.stack), MAX_ERR_STACK) } : {},
|
|
14626
|
+
...err.snippet ? { snippet: clampWithEllipsis(stripAnsi(err.snippet), MAX_ERR_SNIPPET) } : {}
|
|
14627
|
+
};
|
|
14628
|
+
}
|
|
14629
|
+
function mapResultErrors(errors) {
|
|
14630
|
+
if (!errors?.length) return void 0;
|
|
14631
|
+
const out = errors.slice(0, MAX_ERRORS).map((e) => buildErrorInfo(e)).filter((e) => e !== void 0);
|
|
14632
|
+
return out.length > 0 ? out : void 0;
|
|
14633
|
+
}
|
|
14634
|
+
function buildLiveStepTree(steps) {
|
|
14635
|
+
if (!steps?.length) return void 0;
|
|
14636
|
+
const flat = flatten2(steps);
|
|
14637
|
+
if (flat.length === 0) return void 0;
|
|
14638
|
+
const failingIdx = pickFailingIdx2(flat);
|
|
14639
|
+
const failingPath = /* @__PURE__ */ new Set();
|
|
14640
|
+
if (failingIdx >= 0) {
|
|
14641
|
+
failingPath.add(flat[failingIdx].raw);
|
|
14642
|
+
for (const a of flat[failingIdx].ancestors) failingPath.add(flat[a].raw);
|
|
14643
|
+
}
|
|
14644
|
+
const failingLeaf = failingIdx >= 0 ? flat[failingIdx].raw : void 0;
|
|
14645
|
+
for (const level of [0, 1, 2, 3]) {
|
|
14646
|
+
const budget = { left: MAX_TREE_NODES };
|
|
14647
|
+
const tree = buildChildren(steps, {
|
|
14648
|
+
level,
|
|
14649
|
+
depth: 0,
|
|
14650
|
+
underHook: false,
|
|
14651
|
+
underApiOrExpect: false,
|
|
14652
|
+
failingPath,
|
|
14653
|
+
failingLeaf,
|
|
14654
|
+
budget
|
|
14655
|
+
});
|
|
14656
|
+
if (tree.length === 0) return void 0;
|
|
14657
|
+
if (budget.left >= 0 && JSON.stringify(tree).length <= MAX_TREE_JSON) return tree;
|
|
14658
|
+
}
|
|
14659
|
+
return void 0;
|
|
14660
|
+
}
|
|
14661
|
+
function keepNode(s, ctx) {
|
|
14662
|
+
if (ctx.failingPath.has(s)) return true;
|
|
14663
|
+
if (!s.title) return false;
|
|
14664
|
+
const c = s.category ?? "";
|
|
14665
|
+
if (isHook(c) || ctx.underHook) return false;
|
|
14666
|
+
if (c === "test.step") return true;
|
|
14667
|
+
if (ctx.underApiOrExpect) return false;
|
|
14668
|
+
if (c === "expect") return ctx.level < 2;
|
|
14669
|
+
if (c === "pw:api") return ctx.level < 1;
|
|
14670
|
+
return false;
|
|
14671
|
+
}
|
|
14672
|
+
function containsFailing(s, leaf) {
|
|
14673
|
+
if (!leaf) return false;
|
|
14674
|
+
if (s === leaf) return true;
|
|
14675
|
+
return (s.steps ?? []).some((child) => containsFailing(child, leaf));
|
|
14676
|
+
}
|
|
14677
|
+
function buildChildren(nodes, ctx) {
|
|
14678
|
+
const out = [];
|
|
14679
|
+
for (const s of nodes ?? []) {
|
|
14680
|
+
if (ctx.budget.left <= 0) {
|
|
14681
|
+
ctx.budget.left = -1;
|
|
14682
|
+
break;
|
|
14683
|
+
}
|
|
14684
|
+
const c = s.category ?? "";
|
|
14685
|
+
if (!keepNode(s, ctx)) {
|
|
14686
|
+
if (containsFailing(s, ctx.failingLeaf)) {
|
|
14687
|
+
out.push(
|
|
14688
|
+
...buildChildren(s.steps, {
|
|
14689
|
+
...ctx,
|
|
14690
|
+
underHook: ctx.underHook || isHook(c),
|
|
14691
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14692
|
+
})
|
|
14693
|
+
);
|
|
14694
|
+
}
|
|
14695
|
+
continue;
|
|
14696
|
+
}
|
|
14697
|
+
ctx.budget.left -= 1;
|
|
14698
|
+
const node = {
|
|
14699
|
+
t: clampWithEllipsis(stripAnsi(s.title ?? ""), MAX_NODE_TITLE),
|
|
14700
|
+
c: mapCategory(c),
|
|
14701
|
+
...Number.isFinite(s.duration) && s.duration >= 0 ? { d: Math.round(s.duration) } : {},
|
|
14702
|
+
...s.error != null ? { s: "failed" } : {},
|
|
14703
|
+
...s === ctx.failingLeaf ? errField(s) : {}
|
|
14704
|
+
};
|
|
14705
|
+
if (s.steps?.length) {
|
|
14706
|
+
if (ctx.depth + 1 >= MAX_TREE_DEPTH) {
|
|
14707
|
+
if (!node.err && containsFailing(s, ctx.failingLeaf) && s !== ctx.failingLeaf) {
|
|
14708
|
+
const hoisted = errField(ctx.failingLeaf);
|
|
14709
|
+
if (hoisted.err) {
|
|
14710
|
+
node.err = hoisted.err;
|
|
14711
|
+
node.s = "failed";
|
|
14712
|
+
}
|
|
14713
|
+
}
|
|
14714
|
+
} else {
|
|
14715
|
+
const ch = buildChildren(s.steps, {
|
|
14716
|
+
...ctx,
|
|
14717
|
+
depth: ctx.depth + 1,
|
|
14718
|
+
underHook: ctx.underHook || isHook(c),
|
|
14719
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14720
|
+
});
|
|
14721
|
+
if (ch.length > 0) node.ch = ch;
|
|
14722
|
+
}
|
|
14723
|
+
}
|
|
14724
|
+
out.push(node);
|
|
14725
|
+
}
|
|
14726
|
+
return out;
|
|
14727
|
+
}
|
|
14728
|
+
function errField(s) {
|
|
14729
|
+
const err = buildErrorInfo(s.error);
|
|
14730
|
+
return err ? { err } : {};
|
|
14731
|
+
}
|
|
14732
|
+
|
|
14733
|
+
// src/live/event-map.ts
|
|
14542
14734
|
var MAX_TITLE_CHARS2 = 200;
|
|
14543
14735
|
var MAX_STEP_TITLE_CHARS = 200;
|
|
14544
14736
|
var MAX_CONSOLE_CHARS = 2048;
|
|
14737
|
+
var MAX_FILE_CHARS = 300;
|
|
14738
|
+
var MAX_PROJECT_CHARS = 100;
|
|
14739
|
+
var MAX_SUITE_SEGMENTS = 10;
|
|
14740
|
+
function suiteIdentity(test) {
|
|
14741
|
+
const path16 = typeof test.titlePath === "function" ? test.titlePath() : [];
|
|
14742
|
+
if (!Array.isArray(path16) || path16.length < 4) return {};
|
|
14743
|
+
const [, project, file, ...rest] = path16;
|
|
14744
|
+
const describes = rest.slice(0, -1);
|
|
14745
|
+
const out = {};
|
|
14746
|
+
if (typeof file === "string" && file) {
|
|
14747
|
+
out.file = clampWithEllipsis(file.replace(/\\/g, "/"), MAX_FILE_CHARS);
|
|
14748
|
+
}
|
|
14749
|
+
const segs = describes.filter((s) => typeof s === "string" && s.length > 0).slice(0, MAX_SUITE_SEGMENTS).map((s) => clampWithEllipsis(s, MAX_STEP_TITLE_CHARS));
|
|
14750
|
+
if (segs.length > 0) out.suitePath = segs;
|
|
14751
|
+
if (typeof project === "string" && project) {
|
|
14752
|
+
out.projectName = clampWithEllipsis(project, MAX_PROJECT_CHARS);
|
|
14753
|
+
}
|
|
14754
|
+
return out;
|
|
14755
|
+
}
|
|
14545
14756
|
function mapTestBegin(test, shardKey) {
|
|
14546
14757
|
return {
|
|
14547
14758
|
id: test.id,
|
|
14548
14759
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14549
14760
|
status: "running",
|
|
14550
|
-
shardKey
|
|
14761
|
+
shardKey,
|
|
14762
|
+
...suiteIdentity(test)
|
|
14551
14763
|
};
|
|
14552
14764
|
}
|
|
14553
14765
|
function mapTestEnd(test, result, shardKey) {
|
|
14766
|
+
const steps = buildLiveStepTree(result.steps);
|
|
14767
|
+
const errors = mapResultErrors(result.errors);
|
|
14554
14768
|
return {
|
|
14555
14769
|
id: test.id,
|
|
14556
14770
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14557
14771
|
status: result.status,
|
|
14558
14772
|
durationMs: result.duration,
|
|
14559
14773
|
retry: result.retry,
|
|
14560
|
-
shardKey
|
|
14774
|
+
shardKey,
|
|
14775
|
+
...suiteIdentity(test),
|
|
14776
|
+
...steps ? { steps } : {},
|
|
14777
|
+
...errors ? { errors } : {}
|
|
14561
14778
|
};
|
|
14562
14779
|
}
|
|
14563
14780
|
function isLifecycleStep(step) {
|
|
@@ -14607,7 +14824,11 @@ function mapStep(testId, step, phase, shardKey) {
|
|
|
14607
14824
|
startedAt: step.startTime?.getTime(),
|
|
14608
14825
|
// Nesting depth (test.step ancestors) so the page indents children under their
|
|
14609
14826
|
// parent step. 0 → flat, so a test without test.steps renders as a flat trail.
|
|
14610
|
-
depth: testStepDepth2(step)
|
|
14827
|
+
depth: testStepDepth2(step),
|
|
14828
|
+
// Category so the trail styles pw:api actions and expects distinctly. Hooks
|
|
14829
|
+
// and fixtures never reach here non-errored (shouldEmitStep drops them); an
|
|
14830
|
+
// errored one maps to 'api' — the trail has no dedicated hook style.
|
|
14831
|
+
category: step.category === "test.step" ? "step" : step.category === "expect" ? "expect" : "api"
|
|
14611
14832
|
};
|
|
14612
14833
|
}
|
|
14613
14834
|
function mapConsole(kind, chunk, testId, shardKey) {
|
|
@@ -14639,7 +14860,7 @@ var PdfReporter = class {
|
|
|
14639
14860
|
if (this.options.redact.enabled) {
|
|
14640
14861
|
this.redactor = new Redactor(this.options.redact);
|
|
14641
14862
|
}
|
|
14642
|
-
this.version = true ? "0.
|
|
14863
|
+
this.version = true ? "0.25.0" : "0.x";
|
|
14643
14864
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14644
14865
|
this.licenseClient = new LicenseClient({
|
|
14645
14866
|
licenseKey: this.options.licenseKey,
|