@reportforge/playwright-pdf 0.23.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.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +307 -24
- package/dist/index.mjs +307 -24
- 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.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
|
/**
|
|
@@ -14249,6 +14273,7 @@ var LiveStreamer = class {
|
|
|
14249
14273
|
this.enabled = true;
|
|
14250
14274
|
this.gated = false;
|
|
14251
14275
|
this.gatePromise = null;
|
|
14276
|
+
this.announcePromise = null;
|
|
14252
14277
|
this.jwt = null;
|
|
14253
14278
|
this.envSent = false;
|
|
14254
14279
|
this.lastWarnMs = 0;
|
|
@@ -14260,13 +14285,11 @@ var LiveStreamer = class {
|
|
|
14260
14285
|
}
|
|
14261
14286
|
enqueueTest(update) {
|
|
14262
14287
|
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
|
-
}
|
|
14288
|
+
if (this.config.redact) redactTestUpdate(update, this.config.redact);
|
|
14267
14289
|
const prevRetry = this.testStates.get(update.id)?.retry;
|
|
14268
14290
|
this.testStates.set(update.id, { status: update.status, retry: update.retry ?? prevRetry });
|
|
14269
14291
|
this.testBuffer.push(update);
|
|
14292
|
+
this.bufferWeight += update.steps ? TREE_UPDATE_WEIGHT : 1;
|
|
14270
14293
|
this.capPreLicenseBuffer();
|
|
14271
14294
|
this.scheduleFlush();
|
|
14272
14295
|
}
|
|
@@ -14278,9 +14301,62 @@ var LiveStreamer = class {
|
|
|
14278
14301
|
}
|
|
14279
14302
|
event.id = `${this.config.shardKey}:${this.eventSeq++}`;
|
|
14280
14303
|
this.eventBuffer.push(event);
|
|
14304
|
+
this.bufferWeight += 1;
|
|
14281
14305
|
this.capPreLicenseBuffer();
|
|
14282
14306
|
this.scheduleFlush();
|
|
14283
14307
|
}
|
|
14308
|
+
/**
|
|
14309
|
+
* One immediate empty "hello" batch, sent at run start before the banner
|
|
14310
|
+
* prints. It binds the run server-side (so early link-openers and the
|
|
14311
|
+
* dashboard see it instantly instead of a pending window) and returns the
|
|
14312
|
+
* server-ISSUED watch token from the ack — the capability that survives
|
|
14313
|
+
* every license-key lifecycle event, unlike the key-derived HMAC fallback.
|
|
14314
|
+
* Returns null on any failure (not entitled, network error, older server
|
|
14315
|
+
* whose ack carries no token); the caller then falls back to the HMAC link.
|
|
14316
|
+
* Memoized so drain() can await the same in-flight promise (see drain()).
|
|
14317
|
+
*/
|
|
14318
|
+
announce() {
|
|
14319
|
+
return this.announcePromise ?? (this.announcePromise = this.announceOnce());
|
|
14320
|
+
}
|
|
14321
|
+
async announceOnce() {
|
|
14322
|
+
const entitled = await this.ensureGate();
|
|
14323
|
+
if (!entitled || !this.jwt) return null;
|
|
14324
|
+
const body = {
|
|
14325
|
+
runId: this.config.runId,
|
|
14326
|
+
shardKey: this.config.shardKey,
|
|
14327
|
+
shardTotal: this.config.shardTotal,
|
|
14328
|
+
status: "running",
|
|
14329
|
+
counts: this.snapshotCounts(),
|
|
14330
|
+
tests: [],
|
|
14331
|
+
events: [],
|
|
14332
|
+
...this.envSent ? {} : { env: this.config.env }
|
|
14333
|
+
};
|
|
14334
|
+
const controller = new AbortController();
|
|
14335
|
+
const timeout = setTimeout(() => controller.abort(), NETWORK_TIMEOUT_MS3);
|
|
14336
|
+
try {
|
|
14337
|
+
const res = await fetch(`${this.serverUrl}/api/live/ingest`, {
|
|
14338
|
+
method: "POST",
|
|
14339
|
+
headers: {
|
|
14340
|
+
"content-type": "application/json",
|
|
14341
|
+
authorization: `Bearer ${this.jwt}`
|
|
14342
|
+
},
|
|
14343
|
+
body: JSON.stringify(body),
|
|
14344
|
+
signal: controller.signal
|
|
14345
|
+
});
|
|
14346
|
+
if (!res.ok) {
|
|
14347
|
+
await res.body?.cancel().catch(() => {
|
|
14348
|
+
});
|
|
14349
|
+
return null;
|
|
14350
|
+
}
|
|
14351
|
+
const ack = await res.json().catch(() => null);
|
|
14352
|
+
if (body.env) this.envSent = true;
|
|
14353
|
+
return typeof ack?.watchToken === "string" && ack.watchToken ? ack.watchToken : null;
|
|
14354
|
+
} catch {
|
|
14355
|
+
return null;
|
|
14356
|
+
} finally {
|
|
14357
|
+
clearTimeout(timeout);
|
|
14358
|
+
}
|
|
14359
|
+
}
|
|
14284
14360
|
/**
|
|
14285
14361
|
* Final flush: cancels the pending timer, emits the terminal `finished` batch,
|
|
14286
14362
|
* then awaits in-flight requests bounded by LIVE_DRAIN_DEADLINE_MS. Called from
|
|
@@ -14292,6 +14368,7 @@ var LiveStreamer = class {
|
|
|
14292
14368
|
clearTimeout(this.flushTimer);
|
|
14293
14369
|
this.flushTimer = null;
|
|
14294
14370
|
}
|
|
14371
|
+
if (this.announcePromise) await this.announcePromise;
|
|
14295
14372
|
await this.flush("finished");
|
|
14296
14373
|
await Promise.race([
|
|
14297
14374
|
Promise.allSettled([...this.inFlight]),
|
|
@@ -14342,9 +14419,13 @@ var LiveStreamer = class {
|
|
|
14342
14419
|
if (dropEvents > 0) this.eventBuffer.splice(0, dropEvents);
|
|
14343
14420
|
overflow -= dropEvents;
|
|
14344
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);
|
|
14345
14426
|
}
|
|
14346
14427
|
scheduleFlush() {
|
|
14347
|
-
if (this.
|
|
14428
|
+
if (this.bufferWeight >= this.flushMaxBatch) {
|
|
14348
14429
|
void this.flush("running");
|
|
14349
14430
|
return;
|
|
14350
14431
|
}
|
|
@@ -14365,6 +14446,7 @@ var LiveStreamer = class {
|
|
|
14365
14446
|
if (status !== "finished" && tests.length === 0 && events.length === 0) return;
|
|
14366
14447
|
this.testBuffer = [];
|
|
14367
14448
|
this.eventBuffer = [];
|
|
14449
|
+
this.bufferWeight = 0;
|
|
14368
14450
|
const body = {
|
|
14369
14451
|
runId: this.config.runId,
|
|
14370
14452
|
shardKey: this.config.shardKey,
|
|
@@ -14399,6 +14481,7 @@ var LiveStreamer = class {
|
|
|
14399
14481
|
this.enabled = false;
|
|
14400
14482
|
this.testBuffer = [];
|
|
14401
14483
|
this.eventBuffer = [];
|
|
14484
|
+
this.bufferWeight = 0;
|
|
14402
14485
|
logger.debug("Live streaming disabled \u2014 no active license or live entitlement.");
|
|
14403
14486
|
return false;
|
|
14404
14487
|
}
|
|
@@ -14469,7 +14552,7 @@ function computeWatchToken(runId, key) {
|
|
|
14469
14552
|
}
|
|
14470
14553
|
function buildWatchUrl(serverUrl, runId, token) {
|
|
14471
14554
|
const base = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl;
|
|
14472
|
-
return `${base}/live/${encodeURIComponent(runId)}
|
|
14555
|
+
return `${base}/live/${encodeURIComponent(runId)}#t=${encodeURIComponent(token)}`;
|
|
14473
14556
|
}
|
|
14474
14557
|
function formatLiveBanner(url) {
|
|
14475
14558
|
const label = "Live Tracker";
|
|
@@ -14485,25 +14568,213 @@ function shardKeyOf(shard) {
|
|
|
14485
14568
|
|
|
14486
14569
|
// src/live/event-map.ts
|
|
14487
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
|
|
14488
14734
|
var MAX_TITLE_CHARS2 = 200;
|
|
14489
14735
|
var MAX_STEP_TITLE_CHARS = 200;
|
|
14490
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
|
+
}
|
|
14491
14756
|
function mapTestBegin(test, shardKey) {
|
|
14492
14757
|
return {
|
|
14493
14758
|
id: test.id,
|
|
14494
14759
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14495
14760
|
status: "running",
|
|
14496
|
-
shardKey
|
|
14761
|
+
shardKey,
|
|
14762
|
+
...suiteIdentity(test)
|
|
14497
14763
|
};
|
|
14498
14764
|
}
|
|
14499
14765
|
function mapTestEnd(test, result, shardKey) {
|
|
14766
|
+
const steps = buildLiveStepTree(result.steps);
|
|
14767
|
+
const errors = mapResultErrors(result.errors);
|
|
14500
14768
|
return {
|
|
14501
14769
|
id: test.id,
|
|
14502
14770
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14503
14771
|
status: result.status,
|
|
14504
14772
|
durationMs: result.duration,
|
|
14505
14773
|
retry: result.retry,
|
|
14506
|
-
shardKey
|
|
14774
|
+
shardKey,
|
|
14775
|
+
...suiteIdentity(test),
|
|
14776
|
+
...steps ? { steps } : {},
|
|
14777
|
+
...errors ? { errors } : {}
|
|
14507
14778
|
};
|
|
14508
14779
|
}
|
|
14509
14780
|
function isLifecycleStep(step) {
|
|
@@ -14553,7 +14824,11 @@ function mapStep(testId, step, phase, shardKey) {
|
|
|
14553
14824
|
startedAt: step.startTime?.getTime(),
|
|
14554
14825
|
// Nesting depth (test.step ancestors) so the page indents children under their
|
|
14555
14826
|
// parent step. 0 → flat, so a test without test.steps renders as a flat trail.
|
|
14556
|
-
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"
|
|
14557
14832
|
};
|
|
14558
14833
|
}
|
|
14559
14834
|
function mapConsole(kind, chunk, testId, shardKey) {
|
|
@@ -14585,7 +14860,7 @@ var PdfReporter = class {
|
|
|
14585
14860
|
if (this.options.redact.enabled) {
|
|
14586
14861
|
this.redactor = new Redactor(this.options.redact);
|
|
14587
14862
|
}
|
|
14588
|
-
this.version = true ? "0.
|
|
14863
|
+
this.version = true ? "0.25.0" : "0.x";
|
|
14589
14864
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14590
14865
|
this.licenseClient = new LicenseClient({
|
|
14591
14866
|
licenseKey: this.options.licenseKey,
|
|
@@ -14850,18 +15125,8 @@ var PdfReporter = class {
|
|
|
14850
15125
|
this.liveSteps = live.steps;
|
|
14851
15126
|
this.liveConsole = live.console;
|
|
14852
15127
|
void this.license();
|
|
14853
|
-
const token = computeWatchToken(runId, this.options.licenseKey);
|
|
14854
|
-
const watchUrl = token ? buildWatchUrl(serverUrl, runId, token) : void 0;
|
|
14855
15128
|
const env = detectCiEnv();
|
|
14856
15129
|
if (this.redactor && env?.buildUrl) env.buildUrl = this.redactor.redactText(env.buildUrl);
|
|
14857
|
-
if (watchUrl) {
|
|
14858
|
-
void this.announceLive(watchUrl, env, shard).catch(() => {
|
|
14859
|
-
});
|
|
14860
|
-
} else {
|
|
14861
|
-
logger.warn(
|
|
14862
|
-
"Live is enabled but no RF_LICENSE_KEY was found \u2014 cannot mint a watch link or stream. Set RF_LICENSE_KEY (or the `licenseKey` option)."
|
|
14863
|
-
);
|
|
14864
|
-
}
|
|
14865
15130
|
this.liveStreamer = new LiveStreamer({
|
|
14866
15131
|
serverUrl,
|
|
14867
15132
|
runId,
|
|
@@ -14872,16 +15137,27 @@ var PdfReporter = class {
|
|
|
14872
15137
|
env,
|
|
14873
15138
|
flushMs: live.flushMs
|
|
14874
15139
|
});
|
|
15140
|
+
if (!this.options.licenseKey?.trim() && !process.env.RF_LICENSE_KEY?.trim()) {
|
|
15141
|
+
logger.warn(
|
|
15142
|
+
"Live is enabled but no RF_LICENSE_KEY was found \u2014 cannot mint a watch link or stream. Set RF_LICENSE_KEY (or the `licenseKey` option)."
|
|
15143
|
+
);
|
|
15144
|
+
return;
|
|
15145
|
+
}
|
|
15146
|
+
void this.announceLive(serverUrl, runId, env, shard).catch(() => {
|
|
15147
|
+
});
|
|
14875
15148
|
}
|
|
14876
15149
|
/**
|
|
14877
15150
|
* Announces the watch link once the run is confirmed live-entitled. Gating on
|
|
14878
15151
|
* the SAME resolved entitlement the streamer requires (`jwt` + the `'live'`
|
|
14879
15152
|
* feature) guarantees the link is never printed or posted for a run that will
|
|
14880
|
-
* never stream — no dead links. The
|
|
14881
|
-
*
|
|
14882
|
-
*
|
|
15153
|
+
* never stream — no dead links. The link's token is the server-ISSUED one from
|
|
15154
|
+
* the announce handshake (random, key-independent — a key rotation can never
|
|
15155
|
+
* kill the link); the key-derived HMAC is minted only as a fallback when the
|
|
15156
|
+
* handshake fails (network blip, older server). The chat ping fires from the
|
|
15157
|
+
* primary shard only (unsharded, or `shard.current === 1`) so an N-way matrix
|
|
15158
|
+
* posts one message, not N. The banner still prints from every shard's own log.
|
|
14883
15159
|
*/
|
|
14884
|
-
async announceLive(
|
|
15160
|
+
async announceLive(serverUrl, runId, env, shard) {
|
|
14885
15161
|
const info = await this.license();
|
|
14886
15162
|
if (!info) {
|
|
14887
15163
|
logger.warn("Live is enabled but no active license resolved \u2014 no watch link or streaming.");
|
|
@@ -14899,6 +15175,13 @@ var PdfReporter = class {
|
|
|
14899
15175
|
);
|
|
14900
15176
|
return;
|
|
14901
15177
|
}
|
|
15178
|
+
const issued = await this.liveStreamer?.announce();
|
|
15179
|
+
const token = issued ?? computeWatchToken(runId, this.options.licenseKey);
|
|
15180
|
+
if (!token) {
|
|
15181
|
+
logger.warn("Live is enabled but no watch link could be established \u2014 streaming continues without one.");
|
|
15182
|
+
return;
|
|
15183
|
+
}
|
|
15184
|
+
const watchUrl = buildWatchUrl(serverUrl, runId, token);
|
|
14902
15185
|
if (isLevelEnabled("info")) {
|
|
14903
15186
|
console.log(formatLiveBanner(watchUrl));
|
|
14904
15187
|
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{{> head}}
|
|
2
|
-
|
|
3
|
-
{{> watermark}}
|
|
4
|
-
|
|
5
|
-
{{#if sections.coverPage}}{{> cover-page}}{{/if}}
|
|
6
|
-
|
|
7
|
-
<div class="report-header">
|
|
8
|
-
{{#if meta.branding.logoBase64}}
|
|
9
|
-
<img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
|
|
10
|
-
{{/if}}
|
|
11
|
-
<div>
|
|
12
|
-
<div class="report-header__title">{{meta.title}}</div>
|
|
13
|
-
<div class="report-header__meta">{{meta.projectName}} · {{formatDate meta.generatedAt}}</div>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
{{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
|
|
18
|
-
|
|
19
|
-
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
|
-
|
|
21
|
-
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
-
|
|
23
|
-
{{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
24
|
-
|
|
25
|
-
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
|
|
26
|
-
|
|
27
|
-
{{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
|
|
28
|
-
|
|
29
|
-
{{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
|
|
30
|
-
|
|
31
|
-
{{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
|
|
32
|
-
|
|
33
|
-
{{#if sections.defectLog}}{{> defect-log }}{{/if}}
|
|
34
|
-
|
|
35
|
-
<div class="report-footer">
|
|
36
|
-
<span>{{meta.projectName}} · Playwright Test Report</span>
|
|
37
|
-
<span>Generated by ReportForge · {{formatDate meta.generatedAt}}</span>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
</body>
|
|
41
|
-
</html>
|
|
1
|
+
{{> head}}
|
|
2
|
+
|
|
3
|
+
{{> watermark}}
|
|
4
|
+
|
|
5
|
+
{{#if sections.coverPage}}{{> cover-page}}{{/if}}
|
|
6
|
+
|
|
7
|
+
<div class="report-header">
|
|
8
|
+
{{#if meta.branding.logoBase64}}
|
|
9
|
+
<img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
|
|
10
|
+
{{/if}}
|
|
11
|
+
<div>
|
|
12
|
+
<div class="report-header__title">{{meta.title}}</div>
|
|
13
|
+
<div class="report-header__meta">{{meta.projectName}} · {{formatDate meta.generatedAt}}</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
|
|
18
|
+
|
|
19
|
+
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
|
+
|
|
21
|
+
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
+
|
|
23
|
+
{{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
24
|
+
|
|
25
|
+
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
|
|
26
|
+
|
|
27
|
+
{{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
|
|
28
|
+
|
|
29
|
+
{{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
|
|
30
|
+
|
|
31
|
+
{{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
|
|
32
|
+
|
|
33
|
+
{{#if sections.defectLog}}{{> defect-log }}{{/if}}
|
|
34
|
+
|
|
35
|
+
<div class="report-footer">
|
|
36
|
+
<span>{{meta.projectName}} · Playwright Test Report</span>
|
|
37
|
+
<span>Generated by ReportForge · {{formatDate meta.generatedAt}}</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{{> head}}
|
|
2
|
-
|
|
3
|
-
{{> watermark}}
|
|
4
|
-
|
|
5
|
-
{{#if sections.coverPage}}{{> cover-page}}{{/if}}
|
|
6
|
-
|
|
7
|
-
<div class="report-header">
|
|
8
|
-
<div class="report-header__left">
|
|
9
|
-
{{#if meta.branding.logoBase64}}
|
|
10
|
-
<img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
|
|
11
|
-
{{/if}}
|
|
12
|
-
<div class="report-header__title">{{meta.title}}</div>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="report-header__date">{{formatDate meta.generatedAt}}</div>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
{{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
|
|
18
|
-
|
|
19
|
-
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
|
-
|
|
21
|
-
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
-
|
|
23
|
-
{{#if sections.summary}}{{> executive-hero-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
24
|
-
|
|
25
|
-
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend heroTrend=true) }}{{/if}}
|
|
26
|
-
|
|
27
|
-
{{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
|
|
28
|
-
|
|
29
|
-
{{#if sections.defectLog}}{{> defect-log }}{{/if}}
|
|
30
|
-
|
|
31
|
-
{{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
|
|
32
|
-
|
|
33
|
-
{{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
|
|
34
|
-
|
|
35
|
-
<div class="report-footer">
|
|
36
|
-
<span>{{meta.projectName}} · Playwright Test Report</span>
|
|
37
|
-
<span>Generated by ReportForge · {{formatDate meta.generatedAt}}</span>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
</body>
|
|
41
|
-
</html>
|
|
1
|
+
{{> head}}
|
|
2
|
+
|
|
3
|
+
{{> watermark}}
|
|
4
|
+
|
|
5
|
+
{{#if sections.coverPage}}{{> cover-page}}{{/if}}
|
|
6
|
+
|
|
7
|
+
<div class="report-header">
|
|
8
|
+
<div class="report-header__left">
|
|
9
|
+
{{#if meta.branding.logoBase64}}
|
|
10
|
+
<img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
|
|
11
|
+
{{/if}}
|
|
12
|
+
<div class="report-header__title">{{meta.title}}</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="report-header__date">{{formatDate meta.generatedAt}}</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
|
|
18
|
+
|
|
19
|
+
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
|
+
|
|
21
|
+
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
+
|
|
23
|
+
{{#if sections.summary}}{{> executive-hero-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
24
|
+
|
|
25
|
+
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend heroTrend=true) }}{{/if}}
|
|
26
|
+
|
|
27
|
+
{{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
|
|
28
|
+
|
|
29
|
+
{{#if sections.defectLog}}{{> defect-log }}{{/if}}
|
|
30
|
+
|
|
31
|
+
{{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
|
|
32
|
+
|
|
33
|
+
{{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
|
|
34
|
+
|
|
35
|
+
<div class="report-footer">
|
|
36
|
+
<span>{{meta.projectName}} · Playwright Test Report</span>
|
|
37
|
+
<span>Generated by ReportForge · {{formatDate meta.generatedAt}}</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|