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