@reportforge/playwright-pdf 0.5.0 → 0.6.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/dist/index.mjs CHANGED
@@ -10333,6 +10333,113 @@ var DEFAULT_OPTIONS = {
10333
10333
  flakinessTopN: 5
10334
10334
  };
10335
10335
 
10336
+ // src/templates/sections.ts
10337
+ init_esm_shims();
10338
+ var SECTION_KEYS = [
10339
+ // block toggles
10340
+ "coverPage",
10341
+ "analysisOneliner",
10342
+ "releaseGate",
10343
+ "summary",
10344
+ "charts",
10345
+ "trend",
10346
+ "requirementsMatrix",
10347
+ "ciEnvironment",
10348
+ "suiteBreakdown",
10349
+ "failureDeepDive",
10350
+ "failureAnalysis",
10351
+ "slowTests",
10352
+ "defectLog",
10353
+ // display modifiers
10354
+ "passRate",
10355
+ "fullEnvironment",
10356
+ "retries",
10357
+ "fullFailures",
10358
+ "stackTraces"
10359
+ ];
10360
+ var SECTION_DEFAULTS = {
10361
+ minimal: {
10362
+ coverPage: false,
10363
+ analysisOneliner: false,
10364
+ releaseGate: true,
10365
+ summary: true,
10366
+ charts: false,
10367
+ trend: false,
10368
+ requirementsMatrix: false,
10369
+ ciEnvironment: true,
10370
+ suiteBreakdown: true,
10371
+ failureDeepDive: true,
10372
+ failureAnalysis: false,
10373
+ slowTests: false,
10374
+ defectLog: false,
10375
+ passRate: true,
10376
+ fullEnvironment: false,
10377
+ retries: true,
10378
+ fullFailures: true,
10379
+ stackTraces: true
10380
+ },
10381
+ detailed: {
10382
+ coverPage: false,
10383
+ analysisOneliner: false,
10384
+ releaseGate: true,
10385
+ summary: true,
10386
+ charts: true,
10387
+ trend: true,
10388
+ requirementsMatrix: true,
10389
+ ciEnvironment: true,
10390
+ suiteBreakdown: true,
10391
+ failureDeepDive: true,
10392
+ failureAnalysis: true,
10393
+ slowTests: true,
10394
+ defectLog: true,
10395
+ passRate: true,
10396
+ fullEnvironment: true,
10397
+ retries: true,
10398
+ fullFailures: true,
10399
+ stackTraces: true
10400
+ },
10401
+ executive: {
10402
+ coverPage: true,
10403
+ analysisOneliner: true,
10404
+ releaseGate: true,
10405
+ summary: true,
10406
+ charts: true,
10407
+ trend: true,
10408
+ requirementsMatrix: false,
10409
+ ciEnvironment: true,
10410
+ suiteBreakdown: false,
10411
+ failureDeepDive: true,
10412
+ failureAnalysis: false,
10413
+ slowTests: true,
10414
+ defectLog: false,
10415
+ passRate: true,
10416
+ fullEnvironment: false,
10417
+ retries: false,
10418
+ fullFailures: false,
10419
+ stackTraces: false
10420
+ }
10421
+ };
10422
+ var SECTION_KEY_SET = new Set(SECTION_KEYS);
10423
+ function resolveSections(template, userSections) {
10424
+ const base = SECTION_DEFAULTS[template];
10425
+ if (!userSections) return { ...base };
10426
+ const flat = {};
10427
+ for (const [k, v] of Object.entries(userSections)) {
10428
+ if (v !== void 0 && SECTION_KEY_SET.has(k)) flat[k] = v;
10429
+ }
10430
+ const perTemplateRaw = userSections[template] ?? {};
10431
+ const perTemplate = {};
10432
+ for (const [k, v] of Object.entries(perTemplateRaw)) {
10433
+ if (v !== void 0) perTemplate[k] = v;
10434
+ }
10435
+ const result = { ...base, ...flat, ...perTemplate };
10436
+ if (!result.charts) result.trend = false;
10437
+ return result;
10438
+ }
10439
+ function allSectionsOn() {
10440
+ return Object.fromEntries(SECTION_KEYS.map((k) => [k, true]));
10441
+ }
10442
+
10336
10443
  // src/config/schema.ts
10337
10444
  var hexColor = external_exports.string().regex(
10338
10445
  /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,
@@ -10365,6 +10472,15 @@ var failureAnalysisConfigSchema = external_exports.object({
10365
10472
  collectUnclassified: external_exports.boolean().default(true),
10366
10473
  autoUpdateModel: external_exports.boolean().default(true)
10367
10474
  }).default({});
10475
+ var sectionFlagsShape = Object.fromEntries(
10476
+ SECTION_KEYS.map((k) => [k, external_exports.boolean().optional()])
10477
+ );
10478
+ var sectionFlagsSchema = external_exports.object(sectionFlagsShape).strict();
10479
+ var sectionsSchema = sectionFlagsSchema.extend({
10480
+ minimal: sectionFlagsSchema.optional(),
10481
+ detailed: sectionFlagsSchema.optional(),
10482
+ executive: sectionFlagsSchema.optional()
10483
+ }).strict();
10368
10484
  var ReporterOptionsSchema = external_exports.object({
10369
10485
  outputFile: external_exports.string().optional().default(DEFAULT_OPTIONS.outputFile),
10370
10486
  template: external_exports.union([
@@ -10402,7 +10518,8 @@ var ReporterOptionsSchema = external_exports.object({
10402
10518
  ).min(1, "templatePath array must not be empty")
10403
10519
  ]).optional().transform(
10404
10520
  (v) => v === void 0 ? void 0 : Array.isArray(v) ? v : [v]
10405
- )
10521
+ ),
10522
+ sections: sectionsSchema.optional()
10406
10523
  });
10407
10524
  function parseOptions(raw) {
10408
10525
  const result = ReporterOptionsSchema.safeParse(raw ?? {});
@@ -10994,11 +11111,33 @@ var STRENGTH = {
10994
11111
  strongMinMargin: 0.25
10995
11112
  };
10996
11113
  var STRENGTH_LEVELS = ["weak", "moderate", "strong"];
10997
- var FLAKY_MARGIN = 999;
11114
+ var OVERRIDE_MARGIN = 999;
10998
11115
 
10999
11116
  // src/analysis/FailureClusterer.ts
11000
11117
  var MAX_TESTS_PER_CLUSTER = 20;
11001
11118
  var MAX_MESSAGE_CHARS = 200;
11119
+ function errorHeader(message) {
11120
+ const beforeCallLog = message.split(/\n\s*Call log:/i)[0];
11121
+ return beforeCallLog.split("\n").filter((line) => !/^\s*[-+]?\s*(?:Expected|Received)\b/i.test(line)).join("\n");
11122
+ }
11123
+ function isAssertionTimeout(message) {
11124
+ return /Expect "[^"]+" with timeout/.test(message) && /resolved to [1-9]\d* element/.test(message);
11125
+ }
11126
+ function isNetwork(message) {
11127
+ return /net::ERR_(CONNECTION|NAME_NOT_RESOLVED|INTERNET_DISCONNECTED|SSL|TIMED_OUT|ADDRESS|NETWORK_CHANGED|EMPTY_RESPONSE)|\bECONN(?:REFUSED|RESET)\b|\bENOTFOUND\b|\bEAI_AGAIN\b|getaddrinfo|socket hang up|fetch failed/.test(errorHeader(message));
11128
+ }
11129
+ function isNavigation(message) {
11130
+ return /net::ERR_(?:ABORTED|TOO_MANY_REDIRECTS)|navigation interrupted|interrupted by (?:a|another) navigation|because of a navigation|no history entry|page crashed|Execution context was destroyed|frame was detached/.test(errorHeader(message));
11131
+ }
11132
+ function isLocatorNotFound(message) {
11133
+ return /strict mode violation|resolved to 0 element|element not found|not attached to the DOM|No node found for selector|no element matches|Unable to find an element/i.test(errorHeader(message));
11134
+ }
11135
+ var MESSAGE_RULES = [
11136
+ [isAssertionTimeout, "assertion"],
11137
+ [isNavigation, "navigation"],
11138
+ [isNetwork, "network"],
11139
+ [isLocatorNotFound, "locator-not-found"]
11140
+ ];
11002
11141
  function strengthOf(margin) {
11003
11142
  if (margin >= STRENGTH.strongMinMargin) return "strong";
11004
11143
  if (margin >= STRENGTH.moderateMinMargin) return "moderate";
@@ -11021,11 +11160,15 @@ function representativeMessage(message) {
11021
11160
  return firstLine.length > MAX_MESSAGE_CHARS ? `${firstLine.slice(0, MAX_MESSAGE_CHARS)}\u2026` : firstLine;
11022
11161
  }
11023
11162
  function classifyOne(f, model) {
11163
+ const frame = topFrame(f.error.stack);
11024
11164
  if (f.retryHistory.some((r) => r.status === "passed")) {
11025
- return { category: "flaky-by-retry", margin: FLAKY_MARGIN, failure: f, frame: topFrame(f.error.stack) };
11165
+ return { category: "flaky-by-retry", margin: OVERRIDE_MARGIN, failure: f, frame };
11166
+ }
11167
+ for (const [match, category] of MESSAGE_RULES) {
11168
+ if (match(f.error.message)) return { category, margin: OVERRIDE_MARGIN, failure: f, frame };
11026
11169
  }
11027
11170
  const { label, margin } = classify(model, classifyInput(f));
11028
- return { category: label, margin, failure: f, frame: topFrame(f.error.stack) };
11171
+ return { category: label, margin, failure: f, frame };
11029
11172
  }
11030
11173
  function classifyAll(failures, model) {
11031
11174
  return failures.map((f) => classifyOne(f, model));
@@ -11885,11 +12028,26 @@ function formatDuration(ms) {
11885
12028
  }
11886
12029
 
11887
12030
  // src/templates/engine.ts
12031
+ function jsonForInlineScript(value) {
12032
+ return JSON.stringify(value).replace(/<\//g, "<\\/");
12033
+ }
12034
+ function deriveOptions(s) {
12035
+ return {
12036
+ showCharts: s.charts,
12037
+ showTrend: s.trend,
12038
+ showStackTraces: s.stackTraces,
12039
+ showFullEnvironment: s.fullEnvironment,
12040
+ showPassRate: s.passRate,
12041
+ showRetries: s.retries,
12042
+ showFullFailures: s.fullFailures
12043
+ };
12044
+ }
11888
12045
  var TemplateEngine = class {
11889
12046
  constructor() {
11890
12047
  this.chartjsInline = "";
11891
12048
  this.fontsBundleCss = "";
11892
12049
  this.customTemplateCache = /* @__PURE__ */ new Map();
12050
+ this.styleCache = /* @__PURE__ */ new Map();
11893
12051
  this.handlebars = import_handlebars.default.create();
11894
12052
  this.templatesDir = path7.resolve(__dirname, "templates");
11895
12053
  if (!fs5.existsSync(this.templatesDir)) {
@@ -11909,14 +12067,14 @@ var TemplateEngine = class {
11909
12067
  setChartjsBundle(inline) {
11910
12068
  this.chartjsInline = inline;
11911
12069
  }
11912
- render(data, template) {
12070
+ render(data, template, userSections) {
11913
12071
  const layoutPath = path7.join(this.templatesDir, "layouts", `${template}.hbs`);
11914
12072
  if (!fs5.existsSync(layoutPath)) {
11915
12073
  throw new Error(`[reportforge] Template layout not found: ${layoutPath}`);
11916
12074
  }
11917
12075
  const layoutSource = fs5.readFileSync(layoutPath, "utf8");
11918
12076
  const compiledLayout = this.handlebars.compile(layoutSource);
11919
- const ctx = this.buildContext(data, template);
12077
+ const ctx = this.buildContext(data, template, userSections);
11920
12078
  return compiledLayout(ctx);
11921
12079
  }
11922
12080
  renderFromPath(data, filePath) {
@@ -11931,17 +12089,11 @@ var TemplateEngine = class {
11931
12089
  return compiledFn(this.buildCustomContext(data));
11932
12090
  }
11933
12091
  buildCustomContext(data) {
12092
+ const sections = allSectionsOn();
11934
12093
  return {
11935
12094
  ...data,
11936
- options: {
11937
- showCharts: true,
11938
- showTrend: true,
11939
- showStackTraces: true,
11940
- showFullEnvironment: true,
11941
- showPassRate: true,
11942
- showRetries: true,
11943
- showFullFailures: true
11944
- },
12095
+ options: deriveOptions(sections),
12096
+ sections,
11945
12097
  tagGroups: this.buildTagGroups(data),
11946
12098
  slowTests: this.buildSlowTests(data),
11947
12099
  baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
@@ -11949,47 +12101,20 @@ var TemplateEngine = class {
11949
12101
  chartjsScript: this.buildChartjsScript()
11950
12102
  };
11951
12103
  }
11952
- buildContext(data, template) {
12104
+ buildContext(data, template, userSections) {
11953
12105
  const baseCSS = this.fontsBundleCss + "\n" + this.readStyle("base.css");
11954
12106
  const templateCSS = this.readStyle(`${template}.css`);
11955
- const optionsMap = {
11956
- minimal: {
11957
- showCharts: false,
11958
- showTrend: false,
11959
- showStackTraces: true,
11960
- showFullEnvironment: false,
11961
- showPassRate: true,
11962
- showRetries: true,
11963
- showFullFailures: true
11964
- },
11965
- detailed: {
11966
- showCharts: true,
11967
- showTrend: true,
11968
- showStackTraces: true,
11969
- showFullEnvironment: true,
11970
- showPassRate: true,
11971
- showRetries: true,
11972
- showFullFailures: true
11973
- },
11974
- executive: {
11975
- showCharts: true,
11976
- showTrend: true,
11977
- showStackTraces: false,
11978
- showFullEnvironment: false,
11979
- showPassRate: true,
11980
- showRetries: false,
11981
- showFullFailures: false
11982
- }
11983
- };
11984
- const chartjsScript = this.buildChartjsScript();
12107
+ const sections = resolveSections(template, userSections);
12108
+ const options = deriveOptions(sections);
11985
12109
  return {
11986
12110
  ...data,
11987
- options: optionsMap[template],
12111
+ options,
12112
+ sections,
11988
12113
  tagGroups: this.buildTagGroups(data),
11989
12114
  slowTests: this.buildSlowTests(data),
11990
12115
  baseCSS,
11991
12116
  templateCSS,
11992
- chartjsScript
12117
+ chartjsScript: this.buildChartjsScript()
11993
12118
  };
11994
12119
  }
11995
12120
  buildChartjsScript() {
@@ -12037,9 +12162,12 @@ var TemplateEngine = class {
12037
12162
  return tests.sort((a, b) => b.durationMs - a.durationMs).slice(0, 10);
12038
12163
  }
12039
12164
  readStyle(filename) {
12165
+ const cached = this.styleCache.get(filename);
12166
+ if (cached !== void 0) return cached;
12040
12167
  const stylePath = path7.join(this.templatesDir, "styles", filename);
12041
- if (!fs5.existsSync(stylePath)) return "";
12042
- return fs5.readFileSync(stylePath, "utf8");
12168
+ const css = fs5.existsSync(stylePath) ? fs5.readFileSync(stylePath, "utf8") : "";
12169
+ this.styleCache.set(filename, css);
12170
+ return css;
12043
12171
  }
12044
12172
  registerPartials() {
12045
12173
  const partialsDir = path7.join(this.templatesDir, "partials");
@@ -12100,7 +12228,7 @@ var TemplateEngine = class {
12100
12228
  if (!Array.isArray(arr)) return "";
12101
12229
  const values = arr.map((item) => {
12102
12230
  const v = item[key];
12103
- return typeof v === "string" ? JSON.stringify(v) : String(v ?? 0);
12231
+ return typeof v === "string" ? jsonForInlineScript(v) : String(v ?? 0);
12104
12232
  });
12105
12233
  return new import_handlebars.default.SafeString(values.join(","));
12106
12234
  }
@@ -12111,7 +12239,7 @@ var TemplateEngine = class {
12111
12239
  this.handlebars.registerHelper("absFixed", (n) => Math.abs(n).toFixed(0));
12112
12240
  this.handlebars.registerHelper(
12113
12241
  "safeJsonData",
12114
- (value) => new import_handlebars.default.SafeString(JSON.stringify(value).replace(/<\//g, "<\\/"))
12242
+ (value) => new import_handlebars.default.SafeString(jsonForInlineScript(value))
12115
12243
  );
12116
12244
  this.handlebars.registerHelper(
12117
12245
  "reverse",
@@ -12594,6 +12722,12 @@ var PdfGenerator = class {
12594
12722
  `Compression: level=${compression.effective} jpeg-q=${compression.quality} max-w=${compression.maxWidth}px inline-failures\u2264${compression.maxInlineFailures}`
12595
12723
  );
12596
12724
  const outputPaths = [];
12725
+ if (options.sections !== void 0) {
12726
+ const hasCustom = sources.some((s) => s.kind !== "builtin");
12727
+ if (hasCustom) {
12728
+ logger.warn('[reportforge] "sections" applies to built-in templates only; it is ignored for custom templatePath templates.');
12729
+ }
12730
+ }
12597
12731
  const { page } = await this.browserManager.launch(options.puppeteerExecutablePath);
12598
12732
  try {
12599
12733
  for (const source of sources) {
@@ -12701,13 +12835,13 @@ var PdfGenerator = class {
12701
12835
  }
12702
12836
  const templateLabel = source.kind === "builtin" ? source.id : source.label;
12703
12837
  logger.info(`Rendering template: ${templateLabel}`);
12704
- const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id) : this.templateEngine.renderFromPath(renderData, source.absolutePath);
12838
+ const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id, options.sections) : this.templateEngine.renderFromPath(renderData, source.absolutePath);
12705
12839
  const tempHtmlPath = await this.htmlWriter.write(html);
12706
12840
  const fileUrl = this.htmlWriter.toFileUrl(tempHtmlPath);
12707
12841
  try {
12708
12842
  logger.info(`Navigating to temp HTML (${Math.round(html.length / 1024)} KB)...`);
12709
12843
  await page.goto(fileUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
12710
- const showCharts = source.kind === "builtin" ? source.id !== "minimal" : true;
12844
+ const showCharts = source.kind === "builtin" ? resolveSections(source.id, options.sections).charts : true;
12711
12845
  await this.chartRenderer.waitForCharts(page, showCharts);
12712
12846
  logger.info(`Generating PDF \u2192 ${path12.relative(process.cwd(), outputPath)}`);
12713
12847
  await page.pdf({
@@ -13098,7 +13232,7 @@ var PdfReporter = class {
13098
13232
  this.dataCollector = new DataCollector();
13099
13233
  this.pdfGenerator = new PdfGenerator();
13100
13234
  this.options = parseOptions(rawOptions);
13101
- const version = true ? "0.5.0" : "0.x";
13235
+ const version = true ? "0.6.0" : "0.x";
13102
13236
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
13103
13237
  this.licenseClient = new LicenseClient({
13104
13238
  licenseKey: this.options.licenseKey,
@@ -2,6 +2,8 @@
2
2
 
3
3
  {{> watermark}}
4
4
 
5
+ {{#if sections.coverPage}}{{> cover-page}}{{/if}}
6
+
5
7
  <div class="report-header">
6
8
  {{#if meta.branding.logoBase64}}
7
9
  <img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
@@ -12,25 +14,27 @@
12
14
  </div>
13
15
  </div>
14
16
 
15
- {{> release-gate}}
17
+ {{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
18
+
19
+ {{#if sections.releaseGate}}{{> release-gate}}{{/if}}
16
20
 
17
- {{> executive-summary options=(obj showPassRate=true) }}
21
+ {{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate) }}{{/if}}
18
22
 
19
- {{> charts options=(obj showCharts=true showTrend=options.showTrend) }}
23
+ {{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
20
24
 
21
- {{> requirements-matrix }}
25
+ {{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
22
26
 
23
- {{> ci-environment options=(obj showFullEnvironment=true) }}
27
+ {{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
24
28
 
25
- {{> suite-breakdown options=(obj showRetries=true) }}
29
+ {{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries) }}{{/if}}
26
30
 
27
- {{> failure-deep-dive options=(obj showFullFailures=true showStackTraces=true) }}
31
+ {{#if sections.failureDeepDive}}{{> failure-deep-dive options=(obj showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
28
32
 
29
- {{> failure-analysis}}
33
+ {{#if sections.failureAnalysis}}{{> failure-analysis}}{{/if}}
30
34
 
31
- {{> slow-tests limit=10 showProject=true}}
35
+ {{#if sections.slowTests}}{{> slow-tests limit=10 showProject=true}}{{/if}}
32
36
 
33
- {{> defect-log }}
37
+ {{#if sections.defectLog}}{{> defect-log }}{{/if}}
34
38
 
35
39
  <div class="report-footer">
36
40
  <span>Generated by ReportForge</span>
@@ -2,9 +2,9 @@
2
2
 
3
3
  {{> watermark}}
4
4
 
5
- {{> cover-page}}
5
+ {{#if sections.coverPage}}{{> cover-page}}{{/if}}
6
6
 
7
- {{> analysis-oneliner}}
7
+ {{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
8
8
 
9
9
  <div class="report-header">
10
10
  <div class="report-header__left">
@@ -16,17 +16,25 @@
16
16
  <div class="report-header__date">{{formatDate meta.generatedAt}}</div>
17
17
  </div>
18
18
 
19
- {{> release-gate}}
19
+ {{#if sections.releaseGate}}{{> release-gate}}{{/if}}
20
20
 
21
- {{> executive-summary options=(obj showPassRate=true) }}
21
+ {{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate) }}{{/if}}
22
22
 
23
- {{> charts options=(obj showCharts=true showTrend=options.showTrend) }}
23
+ {{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
24
24
 
25
- {{> slow-tests limit=5 showProject=false}}
25
+ {{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
26
26
 
27
- {{> failure-deep-dive options=(obj showFullFailures=false showStackTraces=false) }}
27
+ {{#if sections.slowTests}}{{> slow-tests limit=5 showProject=false}}{{/if}}
28
28
 
29
- {{> ci-environment options=(obj showFullEnvironment=false) }}
29
+ {{#if sections.failureDeepDive}}{{> failure-deep-dive options=(obj showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
30
+
31
+ {{#if sections.failureAnalysis}}{{> failure-analysis}}{{/if}}
32
+
33
+ {{#if sections.defectLog}}{{> defect-log }}{{/if}}
34
+
35
+ {{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
36
+
37
+ {{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries) }}{{/if}}
30
38
 
31
39
  <div class="report-footer">
32
40
  <span>{{meta.projectName}} &nbsp;·&nbsp; Playwright Test Report</span>
@@ -2,6 +2,8 @@
2
2
 
3
3
  {{> watermark}}
4
4
 
5
+ {{#if sections.coverPage}}{{> cover-page}}{{/if}}
6
+
5
7
  <div class="report-header">
6
8
  {{#if meta.branding.logoBase64}}
7
9
  <img class="report-header__logo" src="{{meta.branding.logoBase64}}" alt="Logo">
@@ -12,15 +14,27 @@
12
14
  </div>
13
15
  </div>
14
16
 
15
- {{> release-gate}}
17
+ {{#if sections.analysisOneliner}}{{> analysis-oneliner}}{{/if}}
18
+
19
+ {{#if sections.releaseGate}}{{> release-gate}}{{/if}}
20
+
21
+ {{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate) }}{{/if}}
22
+
23
+ {{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
24
+
25
+ {{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
26
+
27
+ {{#if sections.suiteBreakdown}}{{> suite-breakdown options=(obj showRetries=sections.retries) }}{{/if}}
28
+
29
+ {{#if sections.failureDeepDive}}{{> failure-deep-dive options=(obj showFullFailures=sections.fullFailures showStackTraces=sections.stackTraces) }}{{/if}}
16
30
 
17
- {{> executive-summary options=(obj showPassRate=true showCharts=false) }}
31
+ {{#if sections.failureAnalysis}}{{> failure-analysis}}{{/if}}
18
32
 
19
- {{> suite-breakdown options=(obj showRetries=true) }}
33
+ {{#if sections.slowTests}}{{> slow-tests limit=10 showProject=true}}{{/if}}
20
34
 
21
- {{> failure-deep-dive options=(obj showFullFailures=true showStackTraces=true) }}
35
+ {{#if sections.defectLog}}{{> defect-log }}{{/if}}
22
36
 
23
- {{> ci-environment options=(obj showFullEnvironment=false) }}
37
+ {{#if sections.ciEnvironment}}{{> ci-environment options=(obj showFullEnvironment=sections.fullEnvironment) }}{{/if}}
24
38
 
25
39
  <div class="report-footer">
26
40
  <span>Generated by ReportForge</span>