@reportforge/playwright-pdf 0.0.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +285 -0
  2. package/LICENSE +21 -0
  3. package/README.md +524 -0
  4. package/ci-templates/azure-devops/azure-pipelines.yml +58 -0
  5. package/ci-templates/bitbucket/bitbucket-pipelines.yml +42 -0
  6. package/ci-templates/github-actions/basic-workflow.yml +44 -0
  7. package/ci-templates/github-actions/matrix-workflow.yml +69 -0
  8. package/ci-templates/gitlab-ci/.gitlab-ci.yml +42 -0
  9. package/ci-templates/jenkins/Jenkinsfile +54 -0
  10. package/dist/index.d.mts +272 -0
  11. package/dist/index.d.ts +272 -0
  12. package/dist/index.js +12566 -0
  13. package/dist/index.mjs +12567 -0
  14. package/dist/templates/layouts/detailed.hbs +38 -0
  15. package/dist/templates/layouts/executive.hbs +35 -0
  16. package/dist/templates/layouts/minimal.hbs +29 -0
  17. package/dist/templates/partials/charts.hbs +306 -0
  18. package/dist/templates/partials/ci-environment.hbs +56 -0
  19. package/dist/templates/partials/cover-page.hbs +46 -0
  20. package/dist/templates/partials/defect-log.hbs +27 -0
  21. package/dist/templates/partials/executive-summary.hbs +60 -0
  22. package/dist/templates/partials/failure-deep-dive.hbs +61 -0
  23. package/dist/templates/partials/head.hbs +17 -0
  24. package/dist/templates/partials/release-gate.hbs +63 -0
  25. package/dist/templates/partials/requirements-matrix.hbs +35 -0
  26. package/dist/templates/partials/slow-tests.hbs +27 -0
  27. package/dist/templates/partials/suite-breakdown.hbs +58 -0
  28. package/dist/templates/partials/watermark.hbs +3 -0
  29. package/dist/templates/styles/base.css +234 -0
  30. package/dist/templates/styles/detailed.css +311 -0
  31. package/dist/templates/styles/executive.css +334 -0
  32. package/dist/templates/styles/minimal.css +208 -0
  33. package/package.json +99 -0
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{meta.title}}</title>
7
+ <style>
8
+ :root {
9
+ --primary: {{meta.branding.primaryColor}};
10
+ --accent: {{meta.branding.accentColor}};
11
+ }
12
+ {{{baseCSS}}}
13
+ {{{templateCSS}}}
14
+ </style>
15
+ {{{chartjsScript}}}
16
+ </head>
17
+ <body>
@@ -0,0 +1,63 @@
1
+ {{#if (gt stats.failed 0)}}
2
+ <div class="release-gate release-gate--fail">
3
+ <div class="release-gate__icon">&#x2715;</div>
4
+ <div class="release-gate__body">
5
+ <div class="release-gate__label">Release Recommendation</div>
6
+ <div class="release-gate__verdict release-gate__verdict--fail">
7
+ HOLD &mdash; {{stats.failed}} FAILURE{{#if (gt stats.failed 1)}}S{{/if}}
8
+ </div>
9
+ <div class="release-gate__detail">
10
+ {{stats.failed}} of {{stats.total}} tests failed. Review and fix before shipping.
11
+ {{#if stats.flaky}} &nbsp;&middot;&nbsp; {{stats.flaky}} flaky (additional risk).{{/if}}
12
+ </div>
13
+ </div>
14
+ <div class="release-gate__meta">
15
+ <span>{{environment.branch}}</span>
16
+ <span class="release-gate__sep">&middot;</span>
17
+ <span>{{environment.commit}}</span>
18
+ </div>
19
+ </div>
20
+ {{else}}
21
+ {{#if (gt stats.timedOut 0)}}
22
+ <div class="release-gate release-gate--fail">
23
+ <div class="release-gate__icon">&#x23F1;</div>
24
+ <div class="release-gate__body">
25
+ <div class="release-gate__label">Release Recommendation</div>
26
+ <div class="release-gate__verdict release-gate__verdict--fail">
27
+ HOLD &mdash; {{stats.timedOut}} TIMED OUT
28
+ </div>
29
+ <div class="release-gate__detail">
30
+ {{stats.timedOut}} tests exceeded timeout. Investigate before shipping.
31
+ </div>
32
+ </div>
33
+ <div class="release-gate__meta">
34
+ <span>{{environment.branch}}</span>
35
+ <span class="release-gate__sep">&middot;</span>
36
+ <span>{{environment.commit}}</span>
37
+ </div>
38
+ </div>
39
+ {{else}}
40
+ <div class="release-gate release-gate--pass">
41
+ <div class="release-gate__icon">&#x2713;</div>
42
+ <div class="release-gate__body">
43
+ <div class="release-gate__label">Release Recommendation</div>
44
+ <div class="release-gate__verdict release-gate__verdict--pass">APPROVED TO SHIP</div>
45
+ <div class="release-gate__detail">
46
+ {{stats.passed}} of {{stats.total}} tests passed
47
+ {{#if stats.skipped}}&nbsp;&middot;&nbsp; {{stats.skipped}} skipped{{/if}}
48
+ {{#if stats.flaky}}&nbsp;&middot;&nbsp; {{stats.flaky}} flaky (monitor){{/if}}
49
+ &nbsp;&middot;&nbsp; {{formatDuration stats.duration}}
50
+ </div>
51
+ </div>
52
+ <div class="release-gate__meta">
53
+ <span>{{environment.branch}}</span>
54
+ <span class="release-gate__sep">&middot;</span>
55
+ <span>{{environment.commit}}</span>
56
+ {{#if stats.flaky}}
57
+ <span class="release-gate__sep">&middot;</span>
58
+ <span class="release-gate__flaky-warn">&#9888; {{stats.flaky}} flaky</span>
59
+ {{/if}}
60
+ </div>
61
+ </div>
62
+ {{/if}}
63
+ {{/if}}
@@ -0,0 +1,35 @@
1
+ {{#if tagGroups.length}}
2
+ <div class="section">
3
+ <h2>Requirements Traceability</h2>
4
+ <p style="font-size:10px;color:var(--text-3);margin-bottom:12px;">
5
+ Tests grouped by annotation tags. Add <code>@tag('REQ-001')</code> to link tests to requirements.
6
+ </p>
7
+ <table>
8
+ <thead>
9
+ <tr>
10
+ <th>Requirement / Tag</th>
11
+ <th style="width:60px;">Tests</th>
12
+ <th style="width:60px;">Passed</th>
13
+ <th style="width:60px;">Failed</th>
14
+ <th style="width:120px;">Coverage</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ {{#each tagGroups}}
19
+ <tr class="no-break">
20
+ <td><code style="font-size:9.5px;">{{tag}}</code></td>
21
+ <td>{{total}}</td>
22
+ <td style="color:var(--pass);font-weight:600;">{{passed}}</td>
23
+ <td style="color:{{#if failed}}var(--fail){{else}}var(--pass){{/if}};font-weight:600;">{{failed}}</td>
24
+ <td>
25
+ <div class="coverage-bar">
26
+ <div class="coverage-bar__fill" style="width:{{coveragePct}}%;"></div>
27
+ </div>
28
+ <span style="font-size:9.5px;color:var(--text-3);">{{coveragePct}}%</span>
29
+ </td>
30
+ </tr>
31
+ {{/each}}
32
+ </tbody>
33
+ </table>
34
+ </div>
35
+ {{/if}}
@@ -0,0 +1,27 @@
1
+ {{#if slowTests.length}}
2
+ <div class="section">
3
+ <h2>Slowest Tests</h2>
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>Test</th>
8
+ <th style="width:130px;">Suite</th>
9
+ {{#if showProject}}<th style="width:80px;">Project</th>{{/if}}
10
+ <th style="width:76px;text-align:right;">Duration</th>
11
+ <th style="width:68px;">Status</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ {{#each (take slowTests limit)}}
16
+ <tr class="no-break">
17
+ <td style="font-weight:500;">{{title}}</td>
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}}
20
+ <td style="font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--text-2);text-align:right;">{{durationFormatted}}</td>
21
+ <td><span class="badge badge--{{status}}">{{status}}</span></td>
22
+ </tr>
23
+ {{/each}}
24
+ </tbody>
25
+ </table>
26
+ </div>
27
+ {{/if}}
@@ -0,0 +1,58 @@
1
+ <div class="section">
2
+ <h2>Suite Breakdown</h2>
3
+ {{#each projects}}
4
+ <h3 style="color:var(--primary,#CC785C);margin-bottom:8px;">{{name}}</h3>
5
+ {{#each suites}}
6
+ {{> suite-node this depth=0}}
7
+ {{/each}}
8
+ {{/each}}
9
+ </div>
10
+
11
+ {{#*inline "suite-node"}}
12
+ <div class="suite-group">
13
+ <div class="suite-group__header">
14
+ <span class="suite-title">{{title}}</span>
15
+ <span class="suite-stats">
16
+ <span class="badge badge--passed">{{stats.passed}}</span>
17
+ {{#if stats.failed}}<span class="badge badge--failed">{{stats.failed}}</span>{{/if}}
18
+ {{#if stats.skipped}}<span class="badge badge--skipped">{{stats.skipped}}</span>{{/if}}
19
+ {{#if stats.flaky}}<span class="badge badge--flaky">{{stats.flaky}}</span>{{/if}}
20
+ </span>
21
+ </div>
22
+ {{#if tests.length}}
23
+ <table>
24
+ <thead>
25
+ <tr>
26
+ <th>Test</th>
27
+ <th style="width:88px;">Status</th>
28
+ <th style="width:72px;">Duration</th>
29
+ {{#if ../options.showRetries}}<th style="width:52px;">Retries</th>{{/if}}
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ {{#each tests}}
34
+ <tr class="no-break">
35
+ <td>
36
+ <div style="font-weight:500;">{{title}}</div>
37
+ {{#if location}}<div class="test-path">{{location}}</div>{{/if}}
38
+ {{#if tags.length}}
39
+ <div style="margin-top:3px;">
40
+ {{#each tags}}<code style="font-size:8.5px;background:var(--surface-3);color:var(--text-3);padding:1px 5px;border-radius:3px;margin-right:3px;border:1px solid var(--border);">{{this}}</code>{{/each}}
41
+ </div>
42
+ {{/if}}
43
+ </td>
44
+ <td><span class="badge badge--{{status}}">{{status}}</span></td>
45
+ <td style="color:var(--text-2);font-family:'JetBrains Mono',monospace;font-size:9.5px;">{{formatDuration duration}}</td>
46
+ {{#if ../../options.showRetries}}<td style="color:var(--text-3);text-align:center;">{{retryCount}}</td>{{/if}}
47
+ </tr>
48
+ {{/each}}
49
+ </tbody>
50
+ </table>
51
+ {{/if}}
52
+ {{#each suites}}
53
+ <div style="padding-left:14px;border-left:2px solid var(--border);margin:4px 8px;">
54
+ {{> suite-node this}}
55
+ </div>
56
+ {{/each}}
57
+ </div>
58
+ {{/inline}}
@@ -0,0 +1,3 @@
1
+ {{#if meta.branding.watermark}}
2
+ <div class="watermark" aria-hidden="true">{{meta.branding.watermark}}</div>
3
+ {{/if}}
@@ -0,0 +1,234 @@
1
+ /* ── ReportForge · Claude Design Language · Base ─────────────────────────── */
2
+ /* Fonts (Source Serif Pro, Inter, JetBrains Mono) are inlined as base64
3
+ @font-face declarations by the engine — see scripts/bundle-fonts.ts. */
4
+
5
+ @page {
6
+ size: A4;
7
+ margin: 16mm 14mm 16mm 14mm;
8
+ }
9
+
10
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11
+
12
+ :root {
13
+ --bg: #FAF9F5;
14
+ --surface: #FFFFFF;
15
+ --surface-2: #F2F0E8;
16
+ --surface-3: #ECEAE0;
17
+ --border: #E5E3D8;
18
+ --border-2: #D6D4C8;
19
+ --border-3: #BDBBAF;
20
+ --text-1: #1F1F1C;
21
+ --text-2: #5A5A52;
22
+ --text-3: #8C8B82;
23
+ --accent: #CC785C;
24
+ --accent-soft: #F4E2D9;
25
+ --primary: #CC785C;
26
+ --pass: #3F7D58;
27
+ --pass-dim: #E4EFE4;
28
+ --fail: #C3553C;
29
+ --fail-dim: #F8E8E2;
30
+ --flaky: #CA8A04;
31
+ --flaky-dim: #FEF9C3;
32
+ --skip: #7A786E;
33
+ --skip-dim: #ECEAE0;
34
+ --timeout: #B5803A;
35
+ --timeout-dim: #F4EBD7;
36
+ --info: #2563EB;
37
+ }
38
+
39
+ html, body {
40
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
41
+ font-size: 11px;
42
+ line-height: 1.6;
43
+ color: var(--text-1);
44
+ background: var(--bg);
45
+ }
46
+
47
+ /* ── Typography ─────────────────────────────────────────────────────────── */
48
+ h1 {
49
+ font-family: 'Source Serif Pro', Georgia, serif;
50
+ font-size: 22px; font-weight: 700;
51
+ letter-spacing: -0.2px; color: var(--text-1);
52
+ }
53
+ h2 {
54
+ font-family: 'Inter', system-ui, sans-serif;
55
+ font-size: 10px; font-weight: 700;
56
+ letter-spacing: 1.6px; text-transform: uppercase;
57
+ color: var(--text-2); margin-bottom: 12px; margin-top: 24px;
58
+ display: flex; align-items: center; gap: 10px;
59
+ padding: 4px 0;
60
+ }
61
+ h2::before {
62
+ content: '';
63
+ width: 3px; height: 14px;
64
+ background: var(--accent);
65
+ border-radius: 1px;
66
+ flex-shrink: 0;
67
+ }
68
+ h2::after {
69
+ content: '';
70
+ flex: 1; height: 1px;
71
+ background: var(--border);
72
+ }
73
+ h3 {
74
+ font-family: 'Inter', system-ui, sans-serif;
75
+ font-size: 12px; font-weight: 600;
76
+ color: var(--text-1); margin-bottom: 8px; margin-top: 16px;
77
+ }
78
+ p { margin-bottom: 6px; }
79
+
80
+ a { color: var(--accent); text-decoration: none; }
81
+ a:hover { text-decoration: underline; }
82
+
83
+ /* ── Tables ─────────────────────────────────────────────────────────────── */
84
+ table {
85
+ width: 100%; border-collapse: collapse; font-size: 10.5px;
86
+ }
87
+ thead tr {
88
+ background: var(--surface-2);
89
+ border-bottom: 1px solid var(--border-2);
90
+ }
91
+ th {
92
+ text-align: left; padding: 8px 12px;
93
+ font-family: 'Inter', system-ui, sans-serif; font-size: 9px;
94
+ font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
95
+ color: var(--text-2);
96
+ }
97
+ td {
98
+ padding: 8px 12px;
99
+ border-bottom: 1px solid var(--border);
100
+ vertical-align: top; color: var(--text-1);
101
+ }
102
+ tr:last-child td { border-bottom: none; }
103
+
104
+ /* ── Status badges ──────────────────────────────────────────────────────── */
105
+ .badge {
106
+ display: inline-flex; align-items: center; justify-content: center;
107
+ padding: 2px 9px; border-radius: 4px;
108
+ font-family: 'Inter', system-ui, sans-serif; font-size: 9px;
109
+ font-weight: 600; letter-spacing: 0.6px; text-transform: uppercase;
110
+ white-space: nowrap; min-width: 50px;
111
+ -webkit-print-color-adjust: exact;
112
+ print-color-adjust: exact;
113
+ }
114
+ .badge--passed {
115
+ background: var(--pass-dim); color: var(--pass);
116
+ border: 1px solid #C5DBC9;
117
+ }
118
+ .badge--failed {
119
+ background: var(--fail-dim); color: var(--fail);
120
+ border: 1px solid #E5C9BD;
121
+ }
122
+ .badge--timedout {
123
+ background: var(--timeout-dim); color: var(--timeout);
124
+ border: 1px solid #E5D2A8;
125
+ }
126
+ .badge--skipped {
127
+ background: var(--skip-dim); color: var(--skip);
128
+ border: 1px solid var(--border-2);
129
+ }
130
+ .badge--flaky {
131
+ background: var(--flaky-dim); color: var(--flaky);
132
+ border: 1px solid #E8C99A;
133
+ }
134
+
135
+ /* ── Severity ────────────────────────────────────────────────────────────── */
136
+ .severity--critical { color: var(--fail); font-weight: 700; }
137
+ .severity--high { color: #A35E1E; font-weight: 600; }
138
+ .severity--medium { color: var(--timeout); }
139
+ .severity--low { color: var(--text-3); }
140
+
141
+ /* ── Watermark ──────────────────────────────────────────────────────────── */
142
+ .watermark {
143
+ position: fixed; top: 50%; left: 50%;
144
+ transform: translate(-50%, -50%) rotate(-30deg);
145
+ font-family: 'Source Serif Pro', Georgia, serif; font-size: 60px; font-weight: 700;
146
+ color: rgba(31,31,28,0.04); pointer-events: none; z-index: 0;
147
+ white-space: nowrap; letter-spacing: 8px; text-transform: uppercase;
148
+ user-select: none;
149
+ }
150
+
151
+ /* ── Page breaks ────────────────────────────────────────────────────────── */
152
+ .page-break { page-break-before: always; }
153
+ .no-break { page-break-inside: avoid; }
154
+ .page-group { page-break-before: always; page-break-inside: avoid; break-inside: avoid; }
155
+ .page-group > .section { page-break-inside: avoid; break-inside: avoid; }
156
+ .page-group > .section:last-child { margin-bottom: 0; }
157
+
158
+ /* ── Section wrapper ────────────────────────────────────────────────────── */
159
+ .section { margin-bottom: 24px; page-break-inside: avoid; }
160
+
161
+ /* ── Code / stack trace ─────────────────────────────────────────────────── */
162
+ pre, code {
163
+ font-family: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
164
+ font-size: 9px;
165
+ }
166
+ pre {
167
+ background: var(--surface-2);
168
+ color: var(--text-1);
169
+ border: 1px solid var(--border);
170
+ border-left: 3px solid var(--fail);
171
+ padding: 10px 14px;
172
+ border-radius: 4px;
173
+ overflow-x: auto; white-space: pre-wrap; word-break: break-word;
174
+ max-height: 180px; line-height: 1.7;
175
+ }
176
+ code {
177
+ background: var(--surface-2); color: var(--text-1);
178
+ padding: 1px 6px; border-radius: 3px;
179
+ border: 1px solid var(--border);
180
+ font-size: 9px;
181
+ }
182
+
183
+ /* ── Screenshot ─────────────────────────────────────────────────────────── */
184
+ .screenshot {
185
+ max-width: 100%; max-height: 260px;
186
+ border: 1px solid var(--border-2); border-radius: 4px;
187
+ display: block; margin-top: 10px;
188
+ }
189
+
190
+ /* ── Footer ─────────────────────────────────────────────────────────────── */
191
+ .report-footer {
192
+ margin-top: 36px; padding-top: 14px;
193
+ border-top: 1px solid var(--border);
194
+ font-size: 9px; color: var(--text-3);
195
+ display: flex; justify-content: space-between;
196
+ letter-spacing: 0.6px;
197
+ font-family: 'Inter', system-ui, sans-serif;
198
+ font-weight: 500;
199
+ text-transform: uppercase;
200
+ }
201
+
202
+ /* ── Flaky callout (all templates) ──────────────────────────────────────── */
203
+ .flaky-callout {
204
+ display: flex; align-items: flex-start; gap: 10px;
205
+ padding: 10px 14px; border-radius: 4px; margin-top: 14px;
206
+ background: var(--flaky-dim); border: 1px solid #E8C99A;
207
+ -webkit-print-color-adjust: exact;
208
+ print-color-adjust: exact;
209
+ }
210
+ .flaky-callout__icon {
211
+ font-size: 13px; color: var(--flaky); flex-shrink: 0; margin-top: 1px;
212
+ }
213
+ .flaky-callout__body { display: flex; flex-direction: column; gap: 2px; }
214
+ .flaky-callout__title {
215
+ font-family: 'Inter', system-ui, sans-serif; font-size: 10px;
216
+ font-weight: 700; color: #7A4F00;
217
+ }
218
+ .flaky-callout__text {
219
+ font-family: 'Inter', system-ui, sans-serif; font-size: 9.5px; color: #5C3A00;
220
+ }
221
+
222
+ /* ── Overflow banner (failures spilled to sidecar) ───────────────────────── */
223
+ .overflow-banner {
224
+ margin: 0 0 16px; padding: 10px 14px;
225
+ background: var(--timeout-dim);
226
+ border: 1px solid #E5D2A8;
227
+ border-radius: 4px; font-size: 10px; color: #6B4D1F;
228
+ page-break-inside: avoid;
229
+ }
230
+ .overflow-banner code {
231
+ font-family: 'JetBrains Mono', Consolas, monospace;
232
+ background: rgba(255,255,255,0.6); padding: 1px 4px; border-radius: 2px;
233
+ border: none;
234
+ }