@reportforge/playwright-pdf 0.24.0 → 0.26.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/README.md +9 -6
- 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 +24 -1
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +407 -44
- package/dist/index.mjs +407 -44
- package/dist/templates/layouts/detailed.hbs +43 -41
- package/dist/templates/layouts/executive.hbs +43 -41
- package/dist/templates/layouts/minimal.hbs +43 -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/run-diff.hbs +41 -0
- package/dist/templates/partials/suite-breakdown.hbs +89 -89
- package/dist/templates/partials/watermark.hbs +42 -42
- package/dist/templates/styles/base.css +19 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -10354,6 +10354,7 @@ var SECTION_KEYS = [
|
|
|
10354
10354
|
"slowTests",
|
|
10355
10355
|
"defectLog",
|
|
10356
10356
|
"briefBand",
|
|
10357
|
+
"runDiff",
|
|
10357
10358
|
// display modifiers
|
|
10358
10359
|
"passRate",
|
|
10359
10360
|
"fullEnvironment",
|
|
@@ -10377,6 +10378,7 @@ var SECTION_DEFAULTS = {
|
|
|
10377
10378
|
slowTests: false,
|
|
10378
10379
|
defectLog: false,
|
|
10379
10380
|
briefBand: false,
|
|
10381
|
+
runDiff: true,
|
|
10380
10382
|
passRate: true,
|
|
10381
10383
|
fullEnvironment: false,
|
|
10382
10384
|
retries: true,
|
|
@@ -10398,6 +10400,7 @@ var SECTION_DEFAULTS = {
|
|
|
10398
10400
|
slowTests: true,
|
|
10399
10401
|
defectLog: true,
|
|
10400
10402
|
briefBand: false,
|
|
10403
|
+
runDiff: true,
|
|
10401
10404
|
passRate: true,
|
|
10402
10405
|
fullEnvironment: true,
|
|
10403
10406
|
retries: true,
|
|
@@ -10424,6 +10427,7 @@ var SECTION_DEFAULTS = {
|
|
|
10424
10427
|
slowTests: true,
|
|
10425
10428
|
defectLog: false,
|
|
10426
10429
|
briefBand: true,
|
|
10430
|
+
runDiff: true,
|
|
10427
10431
|
passRate: true,
|
|
10428
10432
|
fullEnvironment: false,
|
|
10429
10433
|
retries: false,
|
|
@@ -13783,6 +13787,9 @@ function buildBranchHash(branch) {
|
|
|
13783
13787
|
async function appendRemoteHistory(opts) {
|
|
13784
13788
|
const numericEntry = { ...opts.entry };
|
|
13785
13789
|
delete numericEntry.flakyTests;
|
|
13790
|
+
delete numericEntry.failedTests;
|
|
13791
|
+
delete numericEntry.failedTestsTruncated;
|
|
13792
|
+
delete numericEntry.branch;
|
|
13786
13793
|
const controller = new AbortController();
|
|
13787
13794
|
const timeout = setTimeout(() => controller.abort(), NETWORK_TIMEOUT_MS2);
|
|
13788
13795
|
try {
|
|
@@ -13812,6 +13819,58 @@ async function appendRemoteHistory(opts) {
|
|
|
13812
13819
|
}
|
|
13813
13820
|
}
|
|
13814
13821
|
|
|
13822
|
+
// src/history/types.ts
|
|
13823
|
+
init_cjs_shims();
|
|
13824
|
+
var FAILED_TESTS_CAP = 300;
|
|
13825
|
+
|
|
13826
|
+
// src/history/RunDiff.ts
|
|
13827
|
+
init_cjs_shims();
|
|
13828
|
+
function splitCurrentFailing(currentFailing, baselineFailed) {
|
|
13829
|
+
const currentFailedIds = /* @__PURE__ */ new Set();
|
|
13830
|
+
const newFailures = [];
|
|
13831
|
+
const stillFailing = [];
|
|
13832
|
+
for (const item of currentFailing) {
|
|
13833
|
+
if (currentFailedIds.has(item.id)) continue;
|
|
13834
|
+
currentFailedIds.add(item.id);
|
|
13835
|
+
(baselineFailed.has(item.id) ? stillFailing : newFailures).push(item);
|
|
13836
|
+
}
|
|
13837
|
+
return { newFailures, stillFailing, currentFailedIds };
|
|
13838
|
+
}
|
|
13839
|
+
function splitBaselineResolved(baselineFailed, currentFailedIds, currentStatuses) {
|
|
13840
|
+
const fixed = [];
|
|
13841
|
+
const noLongerRun = [];
|
|
13842
|
+
for (const [id, title] of baselineFailed) {
|
|
13843
|
+
if (currentFailedIds.has(id)) continue;
|
|
13844
|
+
const status = currentStatuses.get(id);
|
|
13845
|
+
const bucket = status === "passed" || status === "flaky" ? fixed : noLongerRun;
|
|
13846
|
+
bucket.push({ id, title });
|
|
13847
|
+
}
|
|
13848
|
+
return { fixed, noLongerRun };
|
|
13849
|
+
}
|
|
13850
|
+
function computeRunDiff(args) {
|
|
13851
|
+
const { currentFailing, currentStatuses, priorEntries, branch } = args;
|
|
13852
|
+
const baseline = priorEntries.find((e) => e.branch === branch && Array.isArray(e.failedTests));
|
|
13853
|
+
if (!baseline) return null;
|
|
13854
|
+
const baselineFailed = /* @__PURE__ */ new Map();
|
|
13855
|
+
for (const item of baseline.failedTests) {
|
|
13856
|
+
if (!baselineFailed.has(item.id)) baselineFailed.set(item.id, item.title);
|
|
13857
|
+
}
|
|
13858
|
+
const { newFailures, stillFailing, currentFailedIds } = splitCurrentFailing(currentFailing, baselineFailed);
|
|
13859
|
+
const { fixed, noLongerRun } = splitBaselineResolved(baselineFailed, currentFailedIds, currentStatuses);
|
|
13860
|
+
for (const bucket of [newFailures, stillFailing, fixed, noLongerRun]) {
|
|
13861
|
+
bucket.sort((a, b) => a.title.localeCompare(b.title));
|
|
13862
|
+
}
|
|
13863
|
+
return {
|
|
13864
|
+
newFailures,
|
|
13865
|
+
stillFailing,
|
|
13866
|
+
fixed,
|
|
13867
|
+
noLongerRun,
|
|
13868
|
+
baselineRunId: baseline.runId,
|
|
13869
|
+
baselineTimestamp: baseline.timestamp,
|
|
13870
|
+
baselineTruncated: baseline.failedTestsTruncated === true
|
|
13871
|
+
};
|
|
13872
|
+
}
|
|
13873
|
+
|
|
13815
13874
|
// src/history/FlakinessAnalyser.ts
|
|
13816
13875
|
init_cjs_shims();
|
|
13817
13876
|
function computeTopN(entries, topN) {
|
|
@@ -13888,11 +13947,32 @@ function reportName(pdfPaths, reportTitle) {
|
|
|
13888
13947
|
if (pdfPaths.length > 0) return (0, import_path9.basename)(pdfPaths[0]);
|
|
13889
13948
|
return reportTitle || "ReportForge";
|
|
13890
13949
|
}
|
|
13950
|
+
function formatDiffLine(diff) {
|
|
13951
|
+
if (!diff) return null;
|
|
13952
|
+
const newFailures = diff.newFailures.length;
|
|
13953
|
+
const fixed = diff.fixed.length;
|
|
13954
|
+
const stillFailing = diff.stillFailing.length;
|
|
13955
|
+
const noLongerRun = diff.noLongerRun.length;
|
|
13956
|
+
const parts = [
|
|
13957
|
+
`${newFailures} new ${newFailures === 1 ? "failure" : "failures"}`,
|
|
13958
|
+
`${fixed} fixed`,
|
|
13959
|
+
`${stillFailing} still failing`
|
|
13960
|
+
];
|
|
13961
|
+
if (noLongerRun > 0) parts.push(`${noLongerRun} no longer run`);
|
|
13962
|
+
return parts.join(" \xB7 ");
|
|
13963
|
+
}
|
|
13891
13964
|
|
|
13892
13965
|
// src/notify/formatters/slack.ts
|
|
13893
|
-
function buildSlackPayload(stats, pdfPaths, reportTitle) {
|
|
13966
|
+
function buildSlackPayload(stats, pdfPaths, reportTitle, runDiff) {
|
|
13894
13967
|
const label = statusLabel(stats.verdict);
|
|
13895
13968
|
const name = reportName(pdfPaths, reportTitle);
|
|
13969
|
+
const diffLine = formatDiffLine(runDiff);
|
|
13970
|
+
const lines = [
|
|
13971
|
+
`*${label}* \xB7 ${stats.passRate}% passed`,
|
|
13972
|
+
countsLine(stats),
|
|
13973
|
+
...diffLine ? [diffLine] : [],
|
|
13974
|
+
`_${name}_`
|
|
13975
|
+
];
|
|
13896
13976
|
return {
|
|
13897
13977
|
text: `${label} \xB7 ${stats.passRate}% passed`,
|
|
13898
13978
|
blocks: [
|
|
@@ -13900,9 +13980,7 @@ function buildSlackPayload(stats, pdfPaths, reportTitle) {
|
|
|
13900
13980
|
type: "section",
|
|
13901
13981
|
text: {
|
|
13902
13982
|
type: "mrkdwn",
|
|
13903
|
-
text:
|
|
13904
|
-
${countsLine(stats)}
|
|
13905
|
-
_${name}_`
|
|
13983
|
+
text: lines.join("\n")
|
|
13906
13984
|
}
|
|
13907
13985
|
}
|
|
13908
13986
|
]
|
|
@@ -13927,9 +14005,37 @@ function buildTeamsSimpleMessage(text) {
|
|
|
13927
14005
|
]
|
|
13928
14006
|
};
|
|
13929
14007
|
}
|
|
13930
|
-
function buildTeamsPayload(stats, pdfPaths, reportTitle) {
|
|
14008
|
+
function buildTeamsPayload(stats, pdfPaths, reportTitle, runDiff) {
|
|
13931
14009
|
const label = statusLabel(stats.verdict);
|
|
13932
14010
|
const name = reportName(pdfPaths, reportTitle);
|
|
14011
|
+
const diffLine = formatDiffLine(runDiff);
|
|
14012
|
+
const body = [
|
|
14013
|
+
{
|
|
14014
|
+
type: "TextBlock",
|
|
14015
|
+
text: `${label} \xB7 ${stats.passRate}% passed`,
|
|
14016
|
+
weight: "bolder",
|
|
14017
|
+
size: "medium"
|
|
14018
|
+
},
|
|
14019
|
+
{
|
|
14020
|
+
type: "TextBlock",
|
|
14021
|
+
text: countsLine(stats),
|
|
14022
|
+
isSubtle: true
|
|
14023
|
+
},
|
|
14024
|
+
{
|
|
14025
|
+
type: "TextBlock",
|
|
14026
|
+
text: name,
|
|
14027
|
+
isSubtle: true,
|
|
14028
|
+
spacing: "none"
|
|
14029
|
+
}
|
|
14030
|
+
];
|
|
14031
|
+
if (diffLine) {
|
|
14032
|
+
body.push({
|
|
14033
|
+
type: "TextBlock",
|
|
14034
|
+
text: diffLine,
|
|
14035
|
+
isSubtle: true,
|
|
14036
|
+
spacing: "none"
|
|
14037
|
+
});
|
|
14038
|
+
}
|
|
13933
14039
|
return {
|
|
13934
14040
|
type: "message",
|
|
13935
14041
|
attachments: [
|
|
@@ -13939,25 +14045,7 @@ function buildTeamsPayload(stats, pdfPaths, reportTitle) {
|
|
|
13939
14045
|
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
13940
14046
|
type: "AdaptiveCard",
|
|
13941
14047
|
version: "1.4",
|
|
13942
|
-
body
|
|
13943
|
-
{
|
|
13944
|
-
type: "TextBlock",
|
|
13945
|
-
text: `${label} \xB7 ${stats.passRate}% passed`,
|
|
13946
|
-
weight: "bolder",
|
|
13947
|
-
size: "medium"
|
|
13948
|
-
},
|
|
13949
|
-
{
|
|
13950
|
-
type: "TextBlock",
|
|
13951
|
-
text: countsLine(stats),
|
|
13952
|
-
isSubtle: true
|
|
13953
|
-
},
|
|
13954
|
-
{
|
|
13955
|
-
type: "TextBlock",
|
|
13956
|
-
text: name,
|
|
13957
|
-
isSubtle: true,
|
|
13958
|
-
spacing: "none"
|
|
13959
|
-
}
|
|
13960
|
-
]
|
|
14048
|
+
body
|
|
13961
14049
|
}
|
|
13962
14050
|
}
|
|
13963
14051
|
]
|
|
@@ -13966,16 +14054,17 @@ function buildTeamsPayload(stats, pdfPaths, reportTitle) {
|
|
|
13966
14054
|
|
|
13967
14055
|
// src/notify/formatters/discord.ts
|
|
13968
14056
|
init_cjs_shims();
|
|
13969
|
-
function buildDiscordPayload(stats, pdfPaths, reportTitle) {
|
|
14057
|
+
function buildDiscordPayload(stats, pdfPaths, reportTitle, runDiff) {
|
|
13970
14058
|
const label = statusLabel(stats.verdict);
|
|
13971
14059
|
const name = reportName(pdfPaths, reportTitle);
|
|
13972
14060
|
const color = stats.verdict === "passed" ? 3066993 : 15158332;
|
|
14061
|
+
const diffLine = formatDiffLine(runDiff);
|
|
14062
|
+
const descriptionLines = [countsLine(stats), ...diffLine ? [diffLine] : [], name];
|
|
13973
14063
|
return {
|
|
13974
14064
|
embeds: [
|
|
13975
14065
|
{
|
|
13976
14066
|
title: `${label} \xB7 ${stats.passRate}% passed`,
|
|
13977
|
-
description:
|
|
13978
|
-
${name}`,
|
|
14067
|
+
description: descriptionLines.join("\n"),
|
|
13979
14068
|
color
|
|
13980
14069
|
}
|
|
13981
14070
|
]
|
|
@@ -13989,10 +14078,16 @@ var import_path10 = require("path");
|
|
|
13989
14078
|
|
|
13990
14079
|
// src/notify/formatters/email.ts
|
|
13991
14080
|
init_cjs_shims();
|
|
13992
|
-
function buildEmailContent(stats, pdfPaths, reportTitle) {
|
|
14081
|
+
function buildEmailContent(stats, pdfPaths, reportTitle, runDiff) {
|
|
13993
14082
|
const label = statusLabel(stats.verdict);
|
|
13994
|
-
const subject = `[ReportForge] ${label} \
|
|
14083
|
+
const subject = `[ReportForge] ${label} \xB7 ${stats.passRate}% passed`;
|
|
13995
14084
|
const name = reportName(pdfPaths, reportTitle);
|
|
14085
|
+
const diffLine = formatDiffLine(runDiff);
|
|
14086
|
+
const diffRow = diffLine ? `
|
|
14087
|
+
<tr>
|
|
14088
|
+
<td style="padding:8px 12px;border-top:1px solid #e5e5e5">Since last run</td>
|
|
14089
|
+
<td style="padding:8px 12px;border-top:1px solid #e5e5e5;text-align:right">${escHtml(diffLine)}</td>
|
|
14090
|
+
</tr>` : "";
|
|
13996
14091
|
const html = `<!DOCTYPE html>
|
|
13997
14092
|
<html lang="en">
|
|
13998
14093
|
<head><meta charset="utf-8"><title>${escHtml(subject)}</title></head>
|
|
@@ -14027,7 +14122,7 @@ function buildEmailContent(stats, pdfPaths, reportTitle) {
|
|
|
14027
14122
|
<tr style="background:#f9f9f9">
|
|
14028
14123
|
<td style="padding:8px 12px;border-top:1px solid #e5e5e5">Duration</td>
|
|
14029
14124
|
<td style="padding:8px 12px;border-top:1px solid #e5e5e5;text-align:right">${escHtml(countsLine(stats).split(" \xB7 ").pop() ?? "")}</td>
|
|
14030
|
-
</tr
|
|
14125
|
+
</tr>${diffRow}
|
|
14031
14126
|
</table>
|
|
14032
14127
|
<p style="margin:24px 0 0;color:#888;font-size:12px">Sent by ReportForge \xB7 <a href="https://reportforge.org" style="color:#888">reportforge.org</a></p>
|
|
14033
14128
|
</body>
|
|
@@ -14052,7 +14147,8 @@ var EmailSender = class {
|
|
|
14052
14147
|
const { subject, html } = buildEmailContent(
|
|
14053
14148
|
reportData.stats,
|
|
14054
14149
|
pdfPaths,
|
|
14055
|
-
reportData.meta.title ?? ""
|
|
14150
|
+
reportData.meta.title ?? "",
|
|
14151
|
+
reportData.runDiff
|
|
14056
14152
|
);
|
|
14057
14153
|
const body = { from, to: config.to, subject, html };
|
|
14058
14154
|
if (config.attachPdf && pdfPaths.length > 0) {
|
|
@@ -14116,7 +14212,7 @@ var NotificationSender = class {
|
|
|
14116
14212
|
result.skipped.push(name);
|
|
14117
14213
|
return Promise.resolve();
|
|
14118
14214
|
}
|
|
14119
|
-
const payload = FORMATTERS[name](reportData.stats, pdfPaths, reportTitle);
|
|
14215
|
+
const payload = FORMATTERS[name](reportData.stats, pdfPaths, reportTitle, reportData.runDiff);
|
|
14120
14216
|
if (name === "discord" && notifyConfig.discord?.attachPdf && pdfPaths.length > 0) {
|
|
14121
14217
|
return this.postDiscordWithPdf(channel.url, payload, pdfPaths[0], result);
|
|
14122
14218
|
}
|
|
@@ -14230,11 +14326,35 @@ var NETWORK_TIMEOUT_MS3 = 8e3;
|
|
|
14230
14326
|
var LIVE_DRAIN_DEADLINE_MS = 1e4;
|
|
14231
14327
|
var MAX_FLUSH_MS = 3e4;
|
|
14232
14328
|
var WARN_THROTTLE_MS = 3e4;
|
|
14329
|
+
var TREE_UPDATE_WEIGHT = 25;
|
|
14330
|
+
function redactTestUpdate(update, redact) {
|
|
14331
|
+
if (update.title !== void 0) update.title = redact(update.title);
|
|
14332
|
+
if (update.currentStep !== void 0) update.currentStep = redact(update.currentStep);
|
|
14333
|
+
if (update.file !== void 0) update.file = redact(update.file);
|
|
14334
|
+
if (update.suitePath) update.suitePath = update.suitePath.map(redact);
|
|
14335
|
+
if (update.projectName !== void 0) update.projectName = redact(update.projectName);
|
|
14336
|
+
if (update.steps) redactStepNodes(update.steps, redact);
|
|
14337
|
+
if (update.errors) update.errors.forEach((e) => redactError(e, redact));
|
|
14338
|
+
}
|
|
14339
|
+
function redactStepNodes(nodes, redact) {
|
|
14340
|
+
for (const node of nodes) {
|
|
14341
|
+
node.t = redact(node.t);
|
|
14342
|
+
if (node.err) redactError(node.err, redact);
|
|
14343
|
+
if (node.ch) redactStepNodes(node.ch, redact);
|
|
14344
|
+
}
|
|
14345
|
+
}
|
|
14346
|
+
function redactError(err, redact) {
|
|
14347
|
+
err.message = redact(err.message);
|
|
14348
|
+
if (err.stack !== void 0) err.stack = redact(err.stack);
|
|
14349
|
+
if (err.snippet !== void 0) err.snippet = redact(err.snippet);
|
|
14350
|
+
}
|
|
14233
14351
|
var LiveStreamer = class {
|
|
14234
14352
|
constructor(config) {
|
|
14235
14353
|
this.config = config;
|
|
14236
14354
|
this.testBuffer = [];
|
|
14237
14355
|
this.eventBuffer = [];
|
|
14356
|
+
/** Weighted size of the buffers (tree-bearing updates count TREE_UPDATE_WEIGHT). */
|
|
14357
|
+
this.bufferWeight = 0;
|
|
14238
14358
|
/** Monotonic per-shard event counter → stable `{shardKey}:{seq}` event ids. */
|
|
14239
14359
|
this.eventSeq = 0;
|
|
14240
14360
|
/**
|
|
@@ -14260,13 +14380,11 @@ var LiveStreamer = class {
|
|
|
14260
14380
|
}
|
|
14261
14381
|
enqueueTest(update) {
|
|
14262
14382
|
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
|
-
}
|
|
14383
|
+
if (this.config.redact) redactTestUpdate(update, this.config.redact);
|
|
14267
14384
|
const prevRetry = this.testStates.get(update.id)?.retry;
|
|
14268
14385
|
this.testStates.set(update.id, { status: update.status, retry: update.retry ?? prevRetry });
|
|
14269
14386
|
this.testBuffer.push(update);
|
|
14387
|
+
this.bufferWeight += update.steps ? TREE_UPDATE_WEIGHT : 1;
|
|
14270
14388
|
this.capPreLicenseBuffer();
|
|
14271
14389
|
this.scheduleFlush();
|
|
14272
14390
|
}
|
|
@@ -14278,6 +14396,7 @@ var LiveStreamer = class {
|
|
|
14278
14396
|
}
|
|
14279
14397
|
event.id = `${this.config.shardKey}:${this.eventSeq++}`;
|
|
14280
14398
|
this.eventBuffer.push(event);
|
|
14399
|
+
this.bufferWeight += 1;
|
|
14281
14400
|
this.capPreLicenseBuffer();
|
|
14282
14401
|
this.scheduleFlush();
|
|
14283
14402
|
}
|
|
@@ -14395,9 +14514,13 @@ var LiveStreamer = class {
|
|
|
14395
14514
|
if (dropEvents > 0) this.eventBuffer.splice(0, dropEvents);
|
|
14396
14515
|
overflow -= dropEvents;
|
|
14397
14516
|
if (overflow > 0) this.testBuffer.splice(0, overflow);
|
|
14517
|
+
this.bufferWeight = this.currentBufferWeight();
|
|
14518
|
+
}
|
|
14519
|
+
currentBufferWeight() {
|
|
14520
|
+
return this.eventBuffer.length + this.testBuffer.reduce((w, t) => w + (t.steps ? TREE_UPDATE_WEIGHT : 1), 0);
|
|
14398
14521
|
}
|
|
14399
14522
|
scheduleFlush() {
|
|
14400
|
-
if (this.
|
|
14523
|
+
if (this.bufferWeight >= this.flushMaxBatch) {
|
|
14401
14524
|
void this.flush("running");
|
|
14402
14525
|
return;
|
|
14403
14526
|
}
|
|
@@ -14418,6 +14541,7 @@ var LiveStreamer = class {
|
|
|
14418
14541
|
if (status !== "finished" && tests.length === 0 && events.length === 0) return;
|
|
14419
14542
|
this.testBuffer = [];
|
|
14420
14543
|
this.eventBuffer = [];
|
|
14544
|
+
this.bufferWeight = 0;
|
|
14421
14545
|
const body = {
|
|
14422
14546
|
runId: this.config.runId,
|
|
14423
14547
|
shardKey: this.config.shardKey,
|
|
@@ -14452,6 +14576,7 @@ var LiveStreamer = class {
|
|
|
14452
14576
|
this.enabled = false;
|
|
14453
14577
|
this.testBuffer = [];
|
|
14454
14578
|
this.eventBuffer = [];
|
|
14579
|
+
this.bufferWeight = 0;
|
|
14455
14580
|
logger.debug("Live streaming disabled \u2014 no active license or live entitlement.");
|
|
14456
14581
|
return false;
|
|
14457
14582
|
}
|
|
@@ -14538,25 +14663,213 @@ function shardKeyOf(shard) {
|
|
|
14538
14663
|
|
|
14539
14664
|
// src/live/event-map.ts
|
|
14540
14665
|
init_cjs_shims();
|
|
14666
|
+
|
|
14667
|
+
// src/live/step-tree.ts
|
|
14668
|
+
init_cjs_shims();
|
|
14669
|
+
var MAX_TREE_DEPTH = 10;
|
|
14670
|
+
var MAX_TREE_NODES = 150;
|
|
14671
|
+
var MAX_TREE_JSON = 24e3;
|
|
14672
|
+
var MAX_NODE_TITLE = 200;
|
|
14673
|
+
var MAX_ERRORS = 3;
|
|
14674
|
+
var MAX_ERR_MESSAGE = 2e3;
|
|
14675
|
+
var MAX_ERR_STACK = 8192;
|
|
14676
|
+
var MAX_ERR_SNIPPET = 4096;
|
|
14677
|
+
var isHook = (c) => c === "hook" || c === "fixture";
|
|
14678
|
+
function flatten2(steps) {
|
|
14679
|
+
const out = [];
|
|
14680
|
+
const walk = (nodes, depth, ancestors) => {
|
|
14681
|
+
for (const s of nodes ?? []) {
|
|
14682
|
+
const index = out.length;
|
|
14683
|
+
out.push({
|
|
14684
|
+
raw: s,
|
|
14685
|
+
category: s.category ?? "",
|
|
14686
|
+
depth,
|
|
14687
|
+
hasError: s.error != null,
|
|
14688
|
+
ancestors
|
|
14689
|
+
});
|
|
14690
|
+
if (s.steps?.length) walk(s.steps, depth + 1, [...ancestors, index]);
|
|
14691
|
+
}
|
|
14692
|
+
};
|
|
14693
|
+
walk(steps ?? [], 0, []);
|
|
14694
|
+
return out;
|
|
14695
|
+
}
|
|
14696
|
+
var inHookSubtree2 = (flat, f) => isHook(f.category) || f.ancestors.some((a) => isHook(flat[a].category));
|
|
14697
|
+
function pickFailingIdx2(flat) {
|
|
14698
|
+
const pick = (allowTeardown) => {
|
|
14699
|
+
let idx = -1;
|
|
14700
|
+
for (let i = 0; i < flat.length; i++) {
|
|
14701
|
+
const f = flat[i];
|
|
14702
|
+
if (!f.hasError || !allowTeardown && inHookSubtree2(flat, f)) continue;
|
|
14703
|
+
if (idx === -1 || f.depth >= flat[idx].depth) idx = i;
|
|
14704
|
+
}
|
|
14705
|
+
return idx;
|
|
14706
|
+
};
|
|
14707
|
+
const body = pick(false);
|
|
14708
|
+
return body >= 0 ? body : pick(true);
|
|
14709
|
+
}
|
|
14710
|
+
function mapCategory(category) {
|
|
14711
|
+
if (category === "test.step") return "step";
|
|
14712
|
+
if (category === "expect") return "expect";
|
|
14713
|
+
if (isHook(category)) return "hook";
|
|
14714
|
+
return "api";
|
|
14715
|
+
}
|
|
14716
|
+
function buildErrorInfo(err) {
|
|
14717
|
+
if (!err?.message) return void 0;
|
|
14718
|
+
return {
|
|
14719
|
+
message: clampWithEllipsis(stripAnsi(err.message), MAX_ERR_MESSAGE),
|
|
14720
|
+
...err.stack ? { stack: clampWithEllipsis(stripAnsi(err.stack), MAX_ERR_STACK) } : {},
|
|
14721
|
+
...err.snippet ? { snippet: clampWithEllipsis(stripAnsi(err.snippet), MAX_ERR_SNIPPET) } : {}
|
|
14722
|
+
};
|
|
14723
|
+
}
|
|
14724
|
+
function mapResultErrors(errors) {
|
|
14725
|
+
if (!errors?.length) return void 0;
|
|
14726
|
+
const out = errors.slice(0, MAX_ERRORS).map((e) => buildErrorInfo(e)).filter((e) => e !== void 0);
|
|
14727
|
+
return out.length > 0 ? out : void 0;
|
|
14728
|
+
}
|
|
14729
|
+
function buildLiveStepTree(steps) {
|
|
14730
|
+
if (!steps?.length) return void 0;
|
|
14731
|
+
const flat = flatten2(steps);
|
|
14732
|
+
if (flat.length === 0) return void 0;
|
|
14733
|
+
const failingIdx = pickFailingIdx2(flat);
|
|
14734
|
+
const failingPath = /* @__PURE__ */ new Set();
|
|
14735
|
+
if (failingIdx >= 0) {
|
|
14736
|
+
failingPath.add(flat[failingIdx].raw);
|
|
14737
|
+
for (const a of flat[failingIdx].ancestors) failingPath.add(flat[a].raw);
|
|
14738
|
+
}
|
|
14739
|
+
const failingLeaf = failingIdx >= 0 ? flat[failingIdx].raw : void 0;
|
|
14740
|
+
for (const level of [0, 1, 2, 3]) {
|
|
14741
|
+
const budget = { left: MAX_TREE_NODES };
|
|
14742
|
+
const tree = buildChildren(steps, {
|
|
14743
|
+
level,
|
|
14744
|
+
depth: 0,
|
|
14745
|
+
underHook: false,
|
|
14746
|
+
underApiOrExpect: false,
|
|
14747
|
+
failingPath,
|
|
14748
|
+
failingLeaf,
|
|
14749
|
+
budget
|
|
14750
|
+
});
|
|
14751
|
+
if (tree.length === 0) return void 0;
|
|
14752
|
+
if (budget.left >= 0 && JSON.stringify(tree).length <= MAX_TREE_JSON) return tree;
|
|
14753
|
+
}
|
|
14754
|
+
return void 0;
|
|
14755
|
+
}
|
|
14756
|
+
function keepNode(s, ctx) {
|
|
14757
|
+
if (ctx.failingPath.has(s)) return true;
|
|
14758
|
+
if (!s.title) return false;
|
|
14759
|
+
const c = s.category ?? "";
|
|
14760
|
+
if (isHook(c) || ctx.underHook) return false;
|
|
14761
|
+
if (c === "test.step") return true;
|
|
14762
|
+
if (ctx.underApiOrExpect) return false;
|
|
14763
|
+
if (c === "expect") return ctx.level < 2;
|
|
14764
|
+
if (c === "pw:api") return ctx.level < 1;
|
|
14765
|
+
return false;
|
|
14766
|
+
}
|
|
14767
|
+
function containsFailing(s, leaf) {
|
|
14768
|
+
if (!leaf) return false;
|
|
14769
|
+
if (s === leaf) return true;
|
|
14770
|
+
return (s.steps ?? []).some((child) => containsFailing(child, leaf));
|
|
14771
|
+
}
|
|
14772
|
+
function buildChildren(nodes, ctx) {
|
|
14773
|
+
const out = [];
|
|
14774
|
+
for (const s of nodes ?? []) {
|
|
14775
|
+
if (ctx.budget.left <= 0) {
|
|
14776
|
+
ctx.budget.left = -1;
|
|
14777
|
+
break;
|
|
14778
|
+
}
|
|
14779
|
+
const c = s.category ?? "";
|
|
14780
|
+
if (!keepNode(s, ctx)) {
|
|
14781
|
+
if (containsFailing(s, ctx.failingLeaf)) {
|
|
14782
|
+
out.push(
|
|
14783
|
+
...buildChildren(s.steps, {
|
|
14784
|
+
...ctx,
|
|
14785
|
+
underHook: ctx.underHook || isHook(c),
|
|
14786
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14787
|
+
})
|
|
14788
|
+
);
|
|
14789
|
+
}
|
|
14790
|
+
continue;
|
|
14791
|
+
}
|
|
14792
|
+
ctx.budget.left -= 1;
|
|
14793
|
+
const node = {
|
|
14794
|
+
t: clampWithEllipsis(stripAnsi(s.title ?? ""), MAX_NODE_TITLE),
|
|
14795
|
+
c: mapCategory(c),
|
|
14796
|
+
...Number.isFinite(s.duration) && s.duration >= 0 ? { d: Math.round(s.duration) } : {},
|
|
14797
|
+
...s.error != null ? { s: "failed" } : {},
|
|
14798
|
+
...s === ctx.failingLeaf ? errField(s) : {}
|
|
14799
|
+
};
|
|
14800
|
+
if (s.steps?.length) {
|
|
14801
|
+
if (ctx.depth + 1 >= MAX_TREE_DEPTH) {
|
|
14802
|
+
if (!node.err && containsFailing(s, ctx.failingLeaf) && s !== ctx.failingLeaf) {
|
|
14803
|
+
const hoisted = errField(ctx.failingLeaf);
|
|
14804
|
+
if (hoisted.err) {
|
|
14805
|
+
node.err = hoisted.err;
|
|
14806
|
+
node.s = "failed";
|
|
14807
|
+
}
|
|
14808
|
+
}
|
|
14809
|
+
} else {
|
|
14810
|
+
const ch = buildChildren(s.steps, {
|
|
14811
|
+
...ctx,
|
|
14812
|
+
depth: ctx.depth + 1,
|
|
14813
|
+
underHook: ctx.underHook || isHook(c),
|
|
14814
|
+
underApiOrExpect: ctx.underApiOrExpect || c === "pw:api" || c === "expect"
|
|
14815
|
+
});
|
|
14816
|
+
if (ch.length > 0) node.ch = ch;
|
|
14817
|
+
}
|
|
14818
|
+
}
|
|
14819
|
+
out.push(node);
|
|
14820
|
+
}
|
|
14821
|
+
return out;
|
|
14822
|
+
}
|
|
14823
|
+
function errField(s) {
|
|
14824
|
+
const err = buildErrorInfo(s.error);
|
|
14825
|
+
return err ? { err } : {};
|
|
14826
|
+
}
|
|
14827
|
+
|
|
14828
|
+
// src/live/event-map.ts
|
|
14541
14829
|
var MAX_TITLE_CHARS2 = 200;
|
|
14542
14830
|
var MAX_STEP_TITLE_CHARS = 200;
|
|
14543
14831
|
var MAX_CONSOLE_CHARS = 2048;
|
|
14832
|
+
var MAX_FILE_CHARS = 300;
|
|
14833
|
+
var MAX_PROJECT_CHARS = 100;
|
|
14834
|
+
var MAX_SUITE_SEGMENTS = 10;
|
|
14835
|
+
function suiteIdentity(test) {
|
|
14836
|
+
const path15 = typeof test.titlePath === "function" ? test.titlePath() : [];
|
|
14837
|
+
if (!Array.isArray(path15) || path15.length < 4) return {};
|
|
14838
|
+
const [, project, file, ...rest] = path15;
|
|
14839
|
+
const describes = rest.slice(0, -1);
|
|
14840
|
+
const out = {};
|
|
14841
|
+
if (typeof file === "string" && file) {
|
|
14842
|
+
out.file = clampWithEllipsis(file.replace(/\\/g, "/"), MAX_FILE_CHARS);
|
|
14843
|
+
}
|
|
14844
|
+
const segs = describes.filter((s) => typeof s === "string" && s.length > 0).slice(0, MAX_SUITE_SEGMENTS).map((s) => clampWithEllipsis(s, MAX_STEP_TITLE_CHARS));
|
|
14845
|
+
if (segs.length > 0) out.suitePath = segs;
|
|
14846
|
+
if (typeof project === "string" && project) {
|
|
14847
|
+
out.projectName = clampWithEllipsis(project, MAX_PROJECT_CHARS);
|
|
14848
|
+
}
|
|
14849
|
+
return out;
|
|
14850
|
+
}
|
|
14544
14851
|
function mapTestBegin(test, shardKey) {
|
|
14545
14852
|
return {
|
|
14546
14853
|
id: test.id,
|
|
14547
14854
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14548
14855
|
status: "running",
|
|
14549
|
-
shardKey
|
|
14856
|
+
shardKey,
|
|
14857
|
+
...suiteIdentity(test)
|
|
14550
14858
|
};
|
|
14551
14859
|
}
|
|
14552
14860
|
function mapTestEnd(test, result, shardKey) {
|
|
14861
|
+
const steps = buildLiveStepTree(result.steps);
|
|
14862
|
+
const errors = mapResultErrors(result.errors);
|
|
14553
14863
|
return {
|
|
14554
14864
|
id: test.id,
|
|
14555
14865
|
title: clampWithEllipsis(test.title ?? "", MAX_TITLE_CHARS2),
|
|
14556
14866
|
status: result.status,
|
|
14557
14867
|
durationMs: result.duration,
|
|
14558
14868
|
retry: result.retry,
|
|
14559
|
-
shardKey
|
|
14869
|
+
shardKey,
|
|
14870
|
+
...suiteIdentity(test),
|
|
14871
|
+
...steps ? { steps } : {},
|
|
14872
|
+
...errors ? { errors } : {}
|
|
14560
14873
|
};
|
|
14561
14874
|
}
|
|
14562
14875
|
function isLifecycleStep(step) {
|
|
@@ -14606,7 +14919,11 @@ function mapStep(testId, step, phase, shardKey) {
|
|
|
14606
14919
|
startedAt: step.startTime?.getTime(),
|
|
14607
14920
|
// Nesting depth (test.step ancestors) so the page indents children under their
|
|
14608
14921
|
// parent step. 0 → flat, so a test without test.steps renders as a flat trail.
|
|
14609
|
-
depth: testStepDepth2(step)
|
|
14922
|
+
depth: testStepDepth2(step),
|
|
14923
|
+
// Category so the trail styles pw:api actions and expects distinctly. Hooks
|
|
14924
|
+
// and fixtures never reach here non-errored (shouldEmitStep drops them); an
|
|
14925
|
+
// errored one maps to 'api' — the trail has no dedicated hook style.
|
|
14926
|
+
category: step.category === "test.step" ? "step" : step.category === "expect" ? "expect" : "api"
|
|
14610
14927
|
};
|
|
14611
14928
|
}
|
|
14612
14929
|
function mapConsole(kind, chunk, testId, shardKey) {
|
|
@@ -14638,7 +14955,7 @@ var PdfReporter = class {
|
|
|
14638
14955
|
if (this.options.redact.enabled) {
|
|
14639
14956
|
this.redactor = new Redactor(this.options.redact);
|
|
14640
14957
|
}
|
|
14641
|
-
this.version = true ? "0.
|
|
14958
|
+
this.version = true ? "0.26.1" : "0.x";
|
|
14642
14959
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14643
14960
|
this.licenseClient = new LicenseClient({
|
|
14644
14961
|
licenseKey: this.options.licenseKey,
|
|
@@ -14767,6 +15084,7 @@ var PdfReporter = class {
|
|
|
14767
15084
|
const flakyTests = collectFlakyTests(collected.projects).map(
|
|
14768
15085
|
(t) => redactor ? { ...t, title: redactor.redactText(t.title) } : t
|
|
14769
15086
|
);
|
|
15087
|
+
const { redactedFailing, ...failedTestsFields } = buildFailedTestsFields(collected.projects, redactor);
|
|
14770
15088
|
const entry = {
|
|
14771
15089
|
runId: `${(/* @__PURE__ */ new Date()).toISOString()}-${suffix}`,
|
|
14772
15090
|
timestamp: Date.now(),
|
|
@@ -14777,9 +15095,12 @@ var PdfReporter = class {
|
|
|
14777
15095
|
flaky: collected.stats.flaky,
|
|
14778
15096
|
duration: collected.stats.duration,
|
|
14779
15097
|
verdict: deriveVerdict(collected.stats),
|
|
14780
|
-
flakyTests
|
|
15098
|
+
flakyTests,
|
|
15099
|
+
...failedTestsFields,
|
|
15100
|
+
branch: collected.environment.branch
|
|
14781
15101
|
};
|
|
14782
15102
|
const localEntries = hm.append(entry, this.options.historySize);
|
|
15103
|
+
collected.runDiff = resolveRunDiff(collected.projects, redactedFailing, localEntries.slice(1), collected.environment.branch);
|
|
14783
15104
|
let trendEntries = localEntries;
|
|
14784
15105
|
if (this.options.remoteHistory && licenseInfo.jwt) {
|
|
14785
15106
|
const serverUrl = this.options.serverUrl ?? process.env.REPORTFORGE_SERVER_URL ?? DEFAULT_SERVER_URL2;
|
|
@@ -14819,7 +15140,7 @@ var PdfReporter = class {
|
|
|
14819
15140
|
}
|
|
14820
15141
|
}
|
|
14821
15142
|
_buildReportData(collected, licenseInfo) {
|
|
14822
|
-
const { projects, stats, failures, environment, charts } = collected;
|
|
15143
|
+
const { projects, stats, failures, environment, charts, runDiff } = collected;
|
|
14823
15144
|
const projectName = this.options.projectName ?? this.resolveProjectName();
|
|
14824
15145
|
const firstTemplate = this.options.templatePath?.length ? "detailed" : Array.isArray(this.options.template) ? this.options.template[0] : this.options.template;
|
|
14825
15146
|
return {
|
|
@@ -14839,7 +15160,8 @@ var PdfReporter = class {
|
|
|
14839
15160
|
projects,
|
|
14840
15161
|
failures,
|
|
14841
15162
|
environment,
|
|
14842
|
-
charts
|
|
15163
|
+
charts,
|
|
15164
|
+
runDiff
|
|
14843
15165
|
};
|
|
14844
15166
|
}
|
|
14845
15167
|
printsToStdio() {
|
|
@@ -15043,6 +15365,47 @@ function collectFlakyTests(projects) {
|
|
|
15043
15365
|
}
|
|
15044
15366
|
return result;
|
|
15045
15367
|
}
|
|
15368
|
+
function collectFailedTests(projects) {
|
|
15369
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15370
|
+
const result = [];
|
|
15371
|
+
for (const project of projects) {
|
|
15372
|
+
for (const suite of flattenSuiteNodes(project.suites)) {
|
|
15373
|
+
for (const test of suite.tests) {
|
|
15374
|
+
if ((test.status === "failed" || test.status === "timedOut") && !seen.has(test.id)) {
|
|
15375
|
+
seen.add(test.id);
|
|
15376
|
+
result.push({ id: test.id, title: test.fullTitle });
|
|
15377
|
+
}
|
|
15378
|
+
}
|
|
15379
|
+
}
|
|
15380
|
+
}
|
|
15381
|
+
return result;
|
|
15382
|
+
}
|
|
15383
|
+
function collectCurrentStatuses(projects) {
|
|
15384
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
15385
|
+
for (const project of projects) {
|
|
15386
|
+
for (const suite of flattenSuiteNodes(project.suites)) {
|
|
15387
|
+
for (const test of suite.tests) {
|
|
15388
|
+
statuses.set(test.id, test.status);
|
|
15389
|
+
}
|
|
15390
|
+
}
|
|
15391
|
+
}
|
|
15392
|
+
return statuses;
|
|
15393
|
+
}
|
|
15394
|
+
function buildFailedTestsFields(projects, redactor) {
|
|
15395
|
+
const redactedFailing = collectFailedTests(projects).map(
|
|
15396
|
+
(t) => redactor ? { ...t, title: redactor.redactText(t.title) } : t
|
|
15397
|
+
);
|
|
15398
|
+
const failedTests = redactedFailing.slice(0, FAILED_TESTS_CAP);
|
|
15399
|
+
return redactedFailing.length > FAILED_TESTS_CAP ? { redactedFailing, failedTests, failedTestsTruncated: true } : { redactedFailing, failedTests };
|
|
15400
|
+
}
|
|
15401
|
+
function resolveRunDiff(projects, currentFailing, priorEntries, branch) {
|
|
15402
|
+
return computeRunDiff({
|
|
15403
|
+
currentFailing,
|
|
15404
|
+
currentStatuses: collectCurrentStatuses(projects),
|
|
15405
|
+
priorEntries,
|
|
15406
|
+
branch
|
|
15407
|
+
}) ?? void 0;
|
|
15408
|
+
}
|
|
15046
15409
|
|
|
15047
15410
|
// src/index.ts
|
|
15048
15411
|
function defineReporterConfig(options) {
|