@reportforge/playwright-pdf 0.4.0 → 0.5.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/CHANGELOG.md +31 -0
- package/README.md +5 -1
- package/dist/demo/cli.js +16 -2
- package/dist/index.js +88 -46
- package/dist/index.mjs +88 -46
- package/dist/templates/layouts/detailed.hbs +5 -4
- package/dist/templates/layouts/minimal.hbs +2 -0
- package/dist/templates/partials/charts.hbs +30 -16
- package/dist/templates/partials/cover-page.hbs +1 -1
- package/dist/templates/partials/executive-summary.hbs +5 -1
- package/dist/templates/partials/failure-deep-dive.hbs +2 -2
- package/dist/templates/partials/requirements-matrix.hbs +1 -1
- package/dist/templates/partials/slow-tests.hbs +2 -2
- package/dist/templates/partials/suite-breakdown.hbs +2 -1
- package/dist/templates/styles/base.css +244 -6
- package/dist/templates/styles/detailed.css +6 -159
- package/dist/templates/styles/executive.css +3 -76
- package/dist/templates/styles/minimal.css +3 -3
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -11216,16 +11216,31 @@ function runFailureAnalysis(reportData, options, cwd) {
|
|
|
11216
11216
|
// src/collector/DataCollector.ts
|
|
11217
11217
|
init_esm_shims();
|
|
11218
11218
|
|
|
11219
|
+
// src/utils/strip-ansi.ts
|
|
11220
|
+
init_esm_shims();
|
|
11221
|
+
var ANSI_PATTERN = new RegExp(
|
|
11222
|
+
[
|
|
11223
|
+
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
11224
|
+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
|
|
11225
|
+
].join("|"),
|
|
11226
|
+
"g"
|
|
11227
|
+
);
|
|
11228
|
+
function stripAnsi(input) {
|
|
11229
|
+
return input.replace(ANSI_PATTERN, "");
|
|
11230
|
+
}
|
|
11231
|
+
|
|
11219
11232
|
// src/collector/SuiteWalker.ts
|
|
11220
11233
|
init_esm_shims();
|
|
11221
11234
|
|
|
11222
11235
|
// src/collector/stats-utils.ts
|
|
11223
11236
|
init_esm_shims();
|
|
11237
|
+
var TOP_SUITES_FOR_CHART = 10;
|
|
11224
11238
|
function statsFromTests(tests) {
|
|
11225
11239
|
return {
|
|
11226
11240
|
total: tests.length,
|
|
11227
11241
|
passed: tests.filter((t) => t.status === "passed").length,
|
|
11228
|
-
failed: tests.filter((t) => t.status === "failed"
|
|
11242
|
+
failed: tests.filter((t) => t.status === "failed").length,
|
|
11243
|
+
timedOut: tests.filter((t) => t.status === "timedOut").length,
|
|
11229
11244
|
skipped: tests.filter((t) => t.status === "skipped").length,
|
|
11230
11245
|
flaky: tests.filter((t) => t.status === "flaky").length
|
|
11231
11246
|
};
|
|
@@ -11236,12 +11251,32 @@ function aggregateStats(parts) {
|
|
|
11236
11251
|
total: acc.total + s.total,
|
|
11237
11252
|
passed: acc.passed + s.passed,
|
|
11238
11253
|
failed: acc.failed + s.failed,
|
|
11254
|
+
timedOut: acc.timedOut + s.timedOut,
|
|
11239
11255
|
skipped: acc.skipped + s.skipped,
|
|
11240
11256
|
flaky: acc.flaky + s.flaky
|
|
11241
11257
|
}),
|
|
11242
|
-
{ total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
|
|
11258
|
+
{ total: 0, passed: 0, failed: 0, timedOut: 0, skipped: 0, flaky: 0 }
|
|
11243
11259
|
);
|
|
11244
11260
|
}
|
|
11261
|
+
function toRow(s) {
|
|
11262
|
+
return {
|
|
11263
|
+
label: s.title,
|
|
11264
|
+
passed: s.stats.passed,
|
|
11265
|
+
failed: s.stats.failed,
|
|
11266
|
+
timedOut: s.stats.timedOut,
|
|
11267
|
+
skipped: s.stats.skipped
|
|
11268
|
+
};
|
|
11269
|
+
}
|
|
11270
|
+
function buildSuiteResults(projects, limit) {
|
|
11271
|
+
const top = projects.flatMap((p) => p.suites);
|
|
11272
|
+
if (top.length <= 1 && top[0] && top[0].suites.length > 0) {
|
|
11273
|
+
const file = top[0];
|
|
11274
|
+
const fs14 = statsFromTests(file.tests);
|
|
11275
|
+
const rootRow = file.tests.length > 0 ? [{ label: file.title, passed: fs14.passed, failed: fs14.failed, timedOut: fs14.timedOut, skipped: fs14.skipped }] : [];
|
|
11276
|
+
return [...rootRow, ...file.suites.map(toRow)].slice(0, limit);
|
|
11277
|
+
}
|
|
11278
|
+
return top.map(toRow).slice(0, limit);
|
|
11279
|
+
}
|
|
11245
11280
|
|
|
11246
11281
|
// src/collector/SuiteWalker.ts
|
|
11247
11282
|
var SuiteWalker = class {
|
|
@@ -11301,6 +11336,18 @@ var SuiteWalker = class {
|
|
|
11301
11336
|
}
|
|
11302
11337
|
};
|
|
11303
11338
|
|
|
11339
|
+
// src/collector/severity.ts
|
|
11340
|
+
init_esm_shims();
|
|
11341
|
+
var SEVERITY_RANK = {
|
|
11342
|
+
critical: 0,
|
|
11343
|
+
high: 1,
|
|
11344
|
+
medium: 2,
|
|
11345
|
+
low: 3
|
|
11346
|
+
};
|
|
11347
|
+
function sortBySeverity(failures) {
|
|
11348
|
+
return [...failures].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
|
|
11349
|
+
}
|
|
11350
|
+
|
|
11304
11351
|
// src/utils/env.ts
|
|
11305
11352
|
init_esm_shims();
|
|
11306
11353
|
import { execSync } from "child_process";
|
|
@@ -11390,7 +11437,6 @@ function gitCommit() {
|
|
|
11390
11437
|
}
|
|
11391
11438
|
|
|
11392
11439
|
// src/collector/DataCollector.ts
|
|
11393
|
-
var TOP_SUITES_FOR_CHART = 10;
|
|
11394
11440
|
var DataCollector = class {
|
|
11395
11441
|
constructor() {
|
|
11396
11442
|
this.resultMap = /* @__PURE__ */ new Map();
|
|
@@ -11427,13 +11473,13 @@ var DataCollector = class {
|
|
|
11427
11473
|
finalize(fullResult) {
|
|
11428
11474
|
const walker = new SuiteWalker();
|
|
11429
11475
|
const projects = walker.build(this.rootSuite, this.resultMap);
|
|
11430
|
-
const stats = this.computeStats(
|
|
11476
|
+
const stats = this.computeStats(fullResult);
|
|
11431
11477
|
const environment = this.buildEnvironment();
|
|
11432
11478
|
const charts = this.buildChartData(stats, projects);
|
|
11433
11479
|
return {
|
|
11434
11480
|
projects,
|
|
11435
11481
|
stats,
|
|
11436
|
-
failures:
|
|
11482
|
+
failures: sortBySeverity(this.failureRecords),
|
|
11437
11483
|
environment,
|
|
11438
11484
|
charts
|
|
11439
11485
|
};
|
|
@@ -11450,8 +11496,8 @@ var DataCollector = class {
|
|
|
11450
11496
|
testTitle: test.title,
|
|
11451
11497
|
suitePath,
|
|
11452
11498
|
error: {
|
|
11453
|
-
message: error.message ?? String(error),
|
|
11454
|
-
stack: error.stack
|
|
11499
|
+
message: stripAnsi(error.message ?? String(error)),
|
|
11500
|
+
stack: error.stack ? stripAnsi(error.stack) : void 0
|
|
11455
11501
|
},
|
|
11456
11502
|
screenshotPath: screenshotAttachment?.path,
|
|
11457
11503
|
screenshotBuffer: screenshotAttachment?.body,
|
|
@@ -11477,13 +11523,16 @@ var DataCollector = class {
|
|
|
11477
11523
|
if (allLabels.some((l) => l.includes("low") || l.includes("minor"))) return "low";
|
|
11478
11524
|
return "medium";
|
|
11479
11525
|
}
|
|
11480
|
-
computeStats(
|
|
11526
|
+
computeStats(fullResult) {
|
|
11481
11527
|
let total = 0, passed = 0, failed = 0, skipped = 0, flaky = 0, timedOut = 0;
|
|
11482
11528
|
for (const [, result] of this.resultMap) {
|
|
11483
11529
|
total++;
|
|
11484
11530
|
switch (result.status) {
|
|
11531
|
+
// A test that passes on a retry (retry > 0) is flaky, NOT a clean pass.
|
|
11532
|
+
// Counting it in `passed` too would double-count it against `flaky` and
|
|
11533
|
+
// push passRate over 100%.
|
|
11485
11534
|
case "passed":
|
|
11486
|
-
passed++;
|
|
11535
|
+
result.retry > 0 ? flaky++ : passed++;
|
|
11487
11536
|
break;
|
|
11488
11537
|
case "failed":
|
|
11489
11538
|
failed++;
|
|
@@ -11500,13 +11549,6 @@ var DataCollector = class {
|
|
|
11500
11549
|
break;
|
|
11501
11550
|
}
|
|
11502
11551
|
}
|
|
11503
|
-
for (const project of projects) {
|
|
11504
|
-
for (const suite of this.flattenSuites(project.suites)) {
|
|
11505
|
-
for (const test of suite.tests) {
|
|
11506
|
-
if (test.status === "flaky") flaky++;
|
|
11507
|
-
}
|
|
11508
|
-
}
|
|
11509
|
-
}
|
|
11510
11552
|
const duration = fullResult.duration ?? Date.now() - this.startTime;
|
|
11511
11553
|
const passRate = total > 0 ? Math.round((passed + flaky) / total * 100) : 0;
|
|
11512
11554
|
return {
|
|
@@ -11552,27 +11594,18 @@ var DataCollector = class {
|
|
|
11552
11594
|
};
|
|
11553
11595
|
}
|
|
11554
11596
|
buildChartData(stats, projects) {
|
|
11555
|
-
const suiteResults = projects
|
|
11556
|
-
(p) => p.suites.map((s) => ({
|
|
11557
|
-
label: s.title,
|
|
11558
|
-
passed: s.stats.passed,
|
|
11559
|
-
failed: s.stats.failed,
|
|
11560
|
-
skipped: s.stats.skipped
|
|
11561
|
-
}))
|
|
11562
|
-
).slice(0, TOP_SUITES_FOR_CHART);
|
|
11597
|
+
const suiteResults = buildSuiteResults(projects, TOP_SUITES_FOR_CHART);
|
|
11563
11598
|
return {
|
|
11564
11599
|
passRate: {
|
|
11565
11600
|
passed: stats.passed,
|
|
11566
11601
|
failed: stats.failed,
|
|
11602
|
+
timedOut: stats.timedOut,
|
|
11567
11603
|
skipped: stats.skipped,
|
|
11568
11604
|
flaky: stats.flaky
|
|
11569
11605
|
},
|
|
11570
11606
|
suiteResults
|
|
11571
11607
|
};
|
|
11572
11608
|
}
|
|
11573
|
-
flattenSuites(suites) {
|
|
11574
|
-
return suites.flatMap((s) => [s, ...this.flattenSuites(s.suites)]);
|
|
11575
|
-
}
|
|
11576
11609
|
};
|
|
11577
11610
|
|
|
11578
11611
|
// src/collector/ShardMerger.ts
|
|
@@ -11580,15 +11613,14 @@ init_esm_shims();
|
|
|
11580
11613
|
import { readFileSync } from "fs";
|
|
11581
11614
|
import { globSync } from "glob";
|
|
11582
11615
|
import { createRequire } from "module";
|
|
11583
|
-
var TOP_SUITES_FOR_CHART2 = 10;
|
|
11584
11616
|
var ShardMerger = class {
|
|
11585
11617
|
merge(filePaths) {
|
|
11586
11618
|
if (filePaths.length === 0) throw new Error("shardResults: no valid JSON shard files found");
|
|
11587
11619
|
const resolved = this.resolveShardPaths(filePaths);
|
|
11588
11620
|
const reports = this.loadReports(resolved);
|
|
11589
11621
|
const projects = this.mergeProjects(reports);
|
|
11590
|
-
const stats = this.mergeStats(reports);
|
|
11591
|
-
const failures = this.extractFailures(reports);
|
|
11622
|
+
const stats = this.mergeStats(reports, projects);
|
|
11623
|
+
const failures = sortBySeverity(this.extractFailures(reports));
|
|
11592
11624
|
const environment = this.buildEnvironment(reports[0]);
|
|
11593
11625
|
const charts = this.buildCharts(stats, projects);
|
|
11594
11626
|
return { projects, stats, failures, environment, charts };
|
|
@@ -11695,7 +11727,7 @@ var ShardMerger = class {
|
|
|
11695
11727
|
id: spec.id,
|
|
11696
11728
|
title: spec.title,
|
|
11697
11729
|
fullTitle: [...suitePath, spec.title].join(" > "),
|
|
11698
|
-
status: this.resolveStatus(test),
|
|
11730
|
+
status: this.resolveStatus(test, lastResult),
|
|
11699
11731
|
duration: lastResult?.duration ?? 0,
|
|
11700
11732
|
retryCount: test ? test.results.length - 1 : 0,
|
|
11701
11733
|
tags: spec.tags ?? [],
|
|
@@ -11703,8 +11735,9 @@ var ShardMerger = class {
|
|
|
11703
11735
|
location: `${spec.file}:${spec.line}`
|
|
11704
11736
|
};
|
|
11705
11737
|
}
|
|
11706
|
-
resolveStatus(test) {
|
|
11738
|
+
resolveStatus(test, lastResult) {
|
|
11707
11739
|
if (!test) return "skipped";
|
|
11740
|
+
if (lastResult?.status === "timedOut") return "timedOut";
|
|
11708
11741
|
switch (test.status) {
|
|
11709
11742
|
case "expected":
|
|
11710
11743
|
return "passed";
|
|
@@ -11718,7 +11751,7 @@ var ShardMerger = class {
|
|
|
11718
11751
|
return "failed";
|
|
11719
11752
|
}
|
|
11720
11753
|
}
|
|
11721
|
-
mergeStats(reports) {
|
|
11754
|
+
mergeStats(reports, projects) {
|
|
11722
11755
|
let expected = 0, unexpected = 0, flaky = 0, skipped = 0, maxDuration = 0;
|
|
11723
11756
|
for (const r of reports) {
|
|
11724
11757
|
expected += r.stats.expected;
|
|
@@ -11729,13 +11762,14 @@ var ShardMerger = class {
|
|
|
11729
11762
|
}
|
|
11730
11763
|
const total = expected + unexpected + flaky + skipped;
|
|
11731
11764
|
const passRate = total > 0 ? Math.round((expected + flaky) / total * 100) : 0;
|
|
11765
|
+
const timedOut = projects.reduce((n, p) => n + p.stats.timedOut, 0);
|
|
11732
11766
|
return {
|
|
11733
11767
|
total,
|
|
11734
11768
|
passed: expected,
|
|
11735
|
-
failed: unexpected,
|
|
11769
|
+
failed: Math.max(0, unexpected - timedOut),
|
|
11736
11770
|
skipped,
|
|
11737
11771
|
flaky,
|
|
11738
|
-
timedOut
|
|
11772
|
+
timedOut,
|
|
11739
11773
|
duration: maxDuration,
|
|
11740
11774
|
passRate,
|
|
11741
11775
|
verdict: unexpected > 0 ? "failed" : "passed"
|
|
@@ -11773,8 +11807,8 @@ var ShardMerger = class {
|
|
|
11773
11807
|
testTitle: spec.title,
|
|
11774
11808
|
suitePath,
|
|
11775
11809
|
error: {
|
|
11776
|
-
message: error?.message ?? "Test failed",
|
|
11777
|
-
stack: error?.stack
|
|
11810
|
+
message: stripAnsi(error?.message ?? "Test failed"),
|
|
11811
|
+
stack: error?.stack ? stripAnsi(error.stack) : void 0
|
|
11778
11812
|
},
|
|
11779
11813
|
screenshotPath: lastResult?.attachments.find(
|
|
11780
11814
|
(a) => a.name.toLowerCase().includes("screenshot") && a.contentType.startsWith("image/")
|
|
@@ -11813,18 +11847,13 @@ var ShardMerger = class {
|
|
|
11813
11847
|
};
|
|
11814
11848
|
}
|
|
11815
11849
|
buildCharts(stats, projects) {
|
|
11816
|
-
const suiteResults = projects
|
|
11817
|
-
(p) => p.suites.map((s) => ({
|
|
11818
|
-
label: s.title,
|
|
11819
|
-
passed: s.stats.passed,
|
|
11820
|
-
failed: s.stats.failed,
|
|
11821
|
-
skipped: s.stats.skipped
|
|
11822
|
-
}))
|
|
11823
|
-
).slice(0, TOP_SUITES_FOR_CHART2);
|
|
11850
|
+
const suiteResults = buildSuiteResults(projects, TOP_SUITES_FOR_CHART);
|
|
11824
11851
|
return {
|
|
11825
11852
|
passRate: {
|
|
11826
11853
|
passed: stats.passed,
|
|
11827
11854
|
failed: stats.failed,
|
|
11855
|
+
timedOut: stats.timedOut,
|
|
11856
|
+
// always 0 in shard JSON (no separate timedOut count)
|
|
11828
11857
|
skipped: stats.skipped,
|
|
11829
11858
|
flaky: stats.flaky
|
|
11830
11859
|
},
|
|
@@ -12039,6 +12068,19 @@ var TemplateEngine = class {
|
|
|
12039
12068
|
"upperCase",
|
|
12040
12069
|
(s) => typeof s === "string" ? s.toUpperCase() : s
|
|
12041
12070
|
);
|
|
12071
|
+
this.handlebars.registerHelper("verdictLabel", (v) => {
|
|
12072
|
+
const map = {
|
|
12073
|
+
passed: "PASSED",
|
|
12074
|
+
failed: "FAILED",
|
|
12075
|
+
timedout: "TIMED OUT",
|
|
12076
|
+
interrupted: "INTERRUPTED"
|
|
12077
|
+
};
|
|
12078
|
+
return map[v] ?? (typeof v === "string" ? v.toUpperCase() : v);
|
|
12079
|
+
});
|
|
12080
|
+
this.handlebars.registerHelper("coverageBand", (pct) => {
|
|
12081
|
+
const n = Number(pct);
|
|
12082
|
+
return n >= 80 ? "high" : n >= 50 ? "mid" : "low";
|
|
12083
|
+
});
|
|
12042
12084
|
this.handlebars.registerHelper("addOne", (n) => (n ?? 0) + 1);
|
|
12043
12085
|
this.handlebars.registerHelper(
|
|
12044
12086
|
"join",
|
|
@@ -13056,7 +13098,7 @@ var PdfReporter = class {
|
|
|
13056
13098
|
this.dataCollector = new DataCollector();
|
|
13057
13099
|
this.pdfGenerator = new PdfGenerator();
|
|
13058
13100
|
this.options = parseOptions(rawOptions);
|
|
13059
|
-
const version = true ? "0.
|
|
13101
|
+
const version = true ? "0.5.0" : "0.x";
|
|
13060
13102
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
13061
13103
|
this.licenseClient = new LicenseClient({
|
|
13062
13104
|
licenseKey: this.options.licenseKey,
|
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
+
{{> release-gate}}
|
|
16
|
+
|
|
15
17
|
{{> executive-summary options=(obj showPassRate=true) }}
|
|
16
18
|
|
|
17
19
|
{{> charts options=(obj showCharts=true showTrend=options.showTrend) }}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</div>
|
|
21
|
+
{{> requirements-matrix }}
|
|
22
|
+
|
|
23
|
+
{{> ci-environment options=(obj showFullEnvironment=true) }}
|
|
23
24
|
|
|
24
25
|
{{> suite-breakdown options=(obj showRetries=true) }}
|
|
25
26
|
|
|
@@ -71,12 +71,14 @@
|
|
|
71
71
|
var remaining = 0;
|
|
72
72
|
function chartDone() { if (--remaining === 0) window.__chartsReady = true; }
|
|
73
73
|
|
|
74
|
-
var passed
|
|
75
|
-
var failed
|
|
76
|
-
var
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
|
|
74
|
+
var passed = {{charts.passRate.passed}};
|
|
75
|
+
var failed = {{charts.passRate.failed}};
|
|
76
|
+
var timedOut = {{charts.passRate.timedOut}};
|
|
77
|
+
var skipped = {{charts.passRate.skipped}};
|
|
78
|
+
var flaky = {{charts.passRate.flaky}};
|
|
79
|
+
// Pass rate is the canonical server-side stat (flaky counts as pass, denominator
|
|
80
|
+
// is every test) so the doughnut centre can never disagree with the KPI strip.
|
|
81
|
+
var passRate = {{stats.passRate}};
|
|
80
82
|
|
|
81
83
|
/* ── Centre-text plugin for the doughnut ─────────────────────────────── */
|
|
82
84
|
var centerTextPlugin = {
|
|
@@ -107,11 +109,11 @@
|
|
|
107
109
|
new Chart(document.getElementById('passRateChart'), {
|
|
108
110
|
type: 'doughnut',
|
|
109
111
|
data: {
|
|
110
|
-
labels: ['Passed', 'Failed', 'Skipped', 'Flaky'],
|
|
112
|
+
labels: ['Passed', 'Failed', 'Timed Out', 'Skipped', 'Flaky'],
|
|
111
113
|
datasets: [{
|
|
112
|
-
data: [passed, failed, skipped, flaky],
|
|
113
|
-
backgroundColor: ['#3F7D58', '#C3553C', '#7A786E', '#D49452'],
|
|
114
|
-
borderColor: ['#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5'],
|
|
114
|
+
data: [passed, failed, timedOut, skipped, flaky],
|
|
115
|
+
backgroundColor: ['#3F7D58', '#C3553C', '#B5803A', '#7A786E', '#D49452'],
|
|
116
|
+
borderColor: ['#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5'],
|
|
115
117
|
borderWidth: 2,
|
|
116
118
|
hoverOffset: 4
|
|
117
119
|
}]
|
|
@@ -135,8 +137,9 @@
|
|
|
135
137
|
/* ── Horizontal suite results bar ────────────────────────────────────── */
|
|
136
138
|
var suiteLabels = [{{{jsonArray charts.suiteResults 'label'}}}];
|
|
137
139
|
var suitePassed = [{{{jsonArray charts.suiteResults 'passed'}}}];
|
|
138
|
-
var suiteFailed
|
|
139
|
-
var
|
|
140
|
+
var suiteFailed = [{{{jsonArray charts.suiteResults 'failed'}}}];
|
|
141
|
+
var suiteTimedOut = [{{{jsonArray charts.suiteResults 'timedOut'}}}];
|
|
142
|
+
var suiteSkipped = [{{{jsonArray charts.suiteResults 'skipped'}}}];
|
|
140
143
|
|
|
141
144
|
remaining += 1;
|
|
142
145
|
new Chart(document.getElementById('suitesBarChart'), {
|
|
@@ -144,9 +147,10 @@
|
|
|
144
147
|
data: {
|
|
145
148
|
labels: suiteLabels,
|
|
146
149
|
datasets: [
|
|
147
|
-
{ label: 'Passed',
|
|
148
|
-
{ label: 'Failed',
|
|
149
|
-
{ label: '
|
|
150
|
+
{ label: 'Passed', data: suitePassed, backgroundColor: '#3F7D58', borderRadius: 3 },
|
|
151
|
+
{ label: 'Failed', data: suiteFailed, backgroundColor: '#C3553C', borderRadius: 3 },
|
|
152
|
+
{ label: 'Timed Out', data: suiteTimedOut, backgroundColor: '#B5803A', borderRadius: 3 },
|
|
153
|
+
{ label: 'Skipped', data: suiteSkipped, backgroundColor: '#7A786E', borderRadius: 3 }
|
|
150
154
|
]
|
|
151
155
|
},
|
|
152
156
|
options: {
|
|
@@ -161,7 +165,17 @@
|
|
|
161
165
|
},
|
|
162
166
|
y: {
|
|
163
167
|
stacked: true,
|
|
164
|
-
|
|
168
|
+
// Widen the label gutter so the longest label isn't clipped at the canvas edge.
|
|
169
|
+
afterFit: function(scale) { if (!scale.$rfPadded) { scale.$rfPadded = true; scale.width += 8; } },
|
|
170
|
+
ticks: {
|
|
171
|
+
font: { size: 9, family: 'Inter, sans-serif' }, color: '#1F1F1C', autoSkip: false,
|
|
172
|
+
// Truncate long suite / describe labels so they fit the axis gutter
|
|
173
|
+
// instead of clipping at the canvas edge.
|
|
174
|
+
callback: function(value) {
|
|
175
|
+
var l = this.getLabelForValue(value);
|
|
176
|
+
return l.length > 24 ? l.slice(0, 23) + '…' : l;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
165
179
|
grid: { display: false },
|
|
166
180
|
border: { color: '#D6D4C8' }
|
|
167
181
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="section">
|
|
2
2
|
<span class="verdict verdict--{{stats.verdict}}">
|
|
3
|
-
{{
|
|
3
|
+
{{verdictLabel stats.verdict}}
|
|
4
4
|
</span>
|
|
5
5
|
|
|
6
6
|
<div class="kpi-strip">
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
<div class="kpi-card__value">{{stats.failed}}</div>
|
|
20
20
|
<div class="kpi-card__label">Failed</div>
|
|
21
21
|
</div>
|
|
22
|
+
<div class="kpi-card kpi-card--timedout">
|
|
23
|
+
<div class="kpi-card__value">{{stats.timedOut}}</div>
|
|
24
|
+
<div class="kpi-card__label">Timed Out</div>
|
|
25
|
+
</div>
|
|
22
26
|
<div class="kpi-card kpi-card--skip">
|
|
23
27
|
<div class="kpi-card__value">{{stats.skipped}}</div>
|
|
24
28
|
<div class="kpi-card__label">Skipped</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{{#if failures.length}}
|
|
2
|
-
<div class="section
|
|
2
|
+
<div class="section">
|
|
3
3
|
<h2>Failures ({{failures.length}}{{#if overflowFailureCount}} shown · {{overflowFailureCount}} more in sidecar{{/if}})</h2>
|
|
4
4
|
{{#if overflowFailureCount}}
|
|
5
5
|
<div class="overflow-banner">
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
{{/if}}
|
|
11
11
|
{{#each failures}}
|
|
12
|
-
<div class="failure-item no-break">
|
|
12
|
+
<div class="failure-item failure-item--{{severity}} no-break">
|
|
13
13
|
{{#if ../options.showFullFailures}}
|
|
14
14
|
|
|
15
15
|
<div class="failure-item__header">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<td style="color:{{#if failed}}var(--fail){{else}}var(--pass){{/if}};font-weight:600;">{{failed}}</td>
|
|
24
24
|
<td>
|
|
25
25
|
<div class="coverage-bar">
|
|
26
|
-
<div class="coverage-bar__fill" style="width:{{coveragePct}}%;"></div>
|
|
26
|
+
<div class="coverage-bar__fill coverage-bar__fill--{{coverageBand coveragePct}}" style="width:{{coveragePct}}%;"></div>
|
|
27
27
|
</div>
|
|
28
28
|
<span style="font-size:9.5px;color:var(--text-3);">{{coveragePct}}%</span>
|
|
29
29
|
</td>
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
<tr class="no-break">
|
|
17
17
|
<td style="font-weight:500;">{{title}}</td>
|
|
18
18
|
<td style="font-size:9px;color:var(--text-3);font-family:'JetBrains Mono',monospace;">{{suiteTitle}}</td>
|
|
19
|
-
{{#if ../showProject}}<td style="font-size:9px;color:var(--text-3);">{{project}}</td>{{/if}}
|
|
19
|
+
{{#if ../showProject}}<td style="font-size:9px;color:var(--text-3);white-space:nowrap;">{{project}}</td>{{/if}}
|
|
20
20
|
<td style="font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--text-2);text-align:right;">{{durationFormatted}}</td>
|
|
21
|
-
<td
|
|
21
|
+
<td>{{statusBadge status}}</td>
|
|
22
22
|
</tr>
|
|
23
23
|
{{/each}}
|
|
24
24
|
</tbody>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<span class="suite-stats">
|
|
16
16
|
<span class="badge badge--passed">{{stats.passed}}</span>
|
|
17
17
|
{{#if stats.failed}}<span class="badge badge--failed">{{stats.failed}}</span>{{/if}}
|
|
18
|
+
{{#if stats.timedOut}}<span class="badge badge--timedout">{{stats.timedOut}}</span>{{/if}}
|
|
18
19
|
{{#if stats.skipped}}<span class="badge badge--skipped">{{stats.skipped}}</span>{{/if}}
|
|
19
20
|
{{#if stats.flaky}}<span class="badge badge--flaky">{{stats.flaky}}</span>{{/if}}
|
|
20
21
|
</span>
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
</div>
|
|
42
43
|
{{/if}}
|
|
43
44
|
</td>
|
|
44
|
-
<td
|
|
45
|
+
<td>{{statusBadge status}}</td>
|
|
45
46
|
<td style="color:var(--text-2);font-family:'JetBrains Mono',monospace;font-size:9.5px;">{{formatDuration duration}}</td>
|
|
46
47
|
{{#if ../../options.showRetries}}<td style="color:var(--text-3);text-align:center;">{{retryCount}}</td>{{/if}}
|
|
47
48
|
</tr>
|