@sentinelqa/playwright-reporter 0.1.11 → 0.1.12
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/localReport.js +164 -117
- package/package.json +1 -1
package/dist/localReport.js
CHANGED
|
@@ -141,41 +141,89 @@ const copyArtifact = (sourcePath, kind, reportDir, usedRelativePaths, testId) =>
|
|
|
141
141
|
testId
|
|
142
142
|
};
|
|
143
143
|
};
|
|
144
|
-
const
|
|
144
|
+
const createReportTest = (test, titlePath) => {
|
|
145
|
+
const results = Array.isArray(test?.results) ? test.results : [];
|
|
146
|
+
const lastResult = results.length > 0 ? results[results.length - 1] : null;
|
|
147
|
+
const errors = results.flatMap((result) => Array.isArray(result?.errors)
|
|
148
|
+
? result.errors
|
|
149
|
+
.map((error) => error?.message || error?.stack || String(error || ""))
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
: []);
|
|
152
|
+
const duration = results.reduce((total, result) => total + (Number(result?.duration) || 0), 0);
|
|
153
|
+
const id = [
|
|
154
|
+
test?.location?.file || "unknown",
|
|
155
|
+
test?.projectName || "default",
|
|
156
|
+
titlePath.join(" > ")
|
|
157
|
+
].join("::");
|
|
158
|
+
return {
|
|
159
|
+
id,
|
|
160
|
+
title: test?.title || titlePath[titlePath.length - 1] || "Untitled test",
|
|
161
|
+
titlePath,
|
|
162
|
+
file: test?.location?.file || null,
|
|
163
|
+
projectName: test?.projectName || null,
|
|
164
|
+
status: test?.status || lastResult?.status || "unknown",
|
|
165
|
+
duration,
|
|
166
|
+
errors,
|
|
167
|
+
artifacts: []
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const collectTests = (node, parentTitles = []) => {
|
|
171
|
+
const nextTitles = node?.title ? [...parentTitles, node.title] : parentTitles;
|
|
172
|
+
const collected = [];
|
|
173
|
+
if (Array.isArray(node?.tests)) {
|
|
174
|
+
for (const test of node.tests) {
|
|
175
|
+
collected.push(createReportTest(test, [...nextTitles, test?.title].filter(Boolean)));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (Array.isArray(node?.specs)) {
|
|
179
|
+
for (const spec of node.specs) {
|
|
180
|
+
const specTitles = [...nextTitles, spec?.title].filter(Boolean);
|
|
181
|
+
const specTests = Array.isArray(spec?.tests) ? spec.tests : [];
|
|
182
|
+
for (const test of specTests) {
|
|
183
|
+
collected.push(createReportTest(test, specTitles));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (Array.isArray(node?.suites)) {
|
|
188
|
+
for (const suite of node.suites) {
|
|
189
|
+
collected.push(...collectTests(suite, nextTitles));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return collected;
|
|
193
|
+
};
|
|
194
|
+
const collectTestRefs = (node, parentTitles = []) => {
|
|
145
195
|
const nextTitles = node?.title ? [...parentTitles, node.title] : parentTitles;
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const lastResult = results.length > 0 ? results[results.length - 1] : null;
|
|
151
|
-
const errors = results.flatMap((result) => Array.isArray(result?.errors)
|
|
152
|
-
? result.errors
|
|
153
|
-
.map((error) => error?.message || error?.stack || String(error || ""))
|
|
154
|
-
.filter(Boolean)
|
|
155
|
-
: []);
|
|
156
|
-
const duration = results.reduce((total, result) => total + (Number(result?.duration) || 0), 0);
|
|
196
|
+
const refs = [];
|
|
197
|
+
if (Array.isArray(node?.tests)) {
|
|
198
|
+
for (const test of node.tests) {
|
|
199
|
+
const titlePath = [...nextTitles, test?.title].filter(Boolean);
|
|
157
200
|
const id = [
|
|
158
|
-
test
|
|
159
|
-
test
|
|
201
|
+
test?.location?.file || "unknown",
|
|
202
|
+
test?.projectName || "default",
|
|
160
203
|
titlePath.join(" > ")
|
|
161
204
|
].join("::");
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
205
|
+
refs.push({ id, resultList: Array.isArray(test?.results) ? test.results : [] });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(node?.specs)) {
|
|
209
|
+
for (const spec of node.specs) {
|
|
210
|
+
const titlePath = [...nextTitles, spec?.title].filter(Boolean);
|
|
211
|
+
for (const test of Array.isArray(spec?.tests) ? spec.tests : []) {
|
|
212
|
+
const id = [
|
|
213
|
+
test?.location?.file || "unknown",
|
|
214
|
+
test?.projectName || "default",
|
|
215
|
+
titlePath.join(" > ")
|
|
216
|
+
].join("::");
|
|
217
|
+
refs.push({ id, resultList: Array.isArray(test?.results) ? test.results : [] });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (Array.isArray(node?.suites)) {
|
|
222
|
+
for (const suite of node.suites) {
|
|
223
|
+
refs.push(...collectTestRefs(suite, nextTitles));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return refs;
|
|
179
227
|
};
|
|
180
228
|
const summarizeTests = (tests) => {
|
|
181
229
|
return tests.reduce((summary, test) => {
|
|
@@ -235,8 +283,8 @@ const renderTestCard = (test) => {
|
|
|
235
283
|
? test.artifacts.map((artifact) => renderArtifact(artifact)).join("\n")
|
|
236
284
|
: `<div class="empty-state">No test-linked artifacts were detected for this result.</div>`;
|
|
237
285
|
return `
|
|
238
|
-
<
|
|
239
|
-
<
|
|
286
|
+
<details class="test-card">
|
|
287
|
+
<summary class="test-summary">
|
|
240
288
|
<div>
|
|
241
289
|
<div class="status-pill ${statusClass}">${escapeHtml(test.status)}</div>
|
|
242
290
|
<h3>${escapeHtml(test.titlePath.join(" > ") || test.title)}</h3>
|
|
@@ -246,7 +294,7 @@ const renderTestCard = (test) => {
|
|
|
246
294
|
${projectLine}
|
|
247
295
|
<div class="meta-item">Duration: ${escapeHtml(formatDuration(test.duration))}</div>
|
|
248
296
|
</div>
|
|
249
|
-
</
|
|
297
|
+
</summary>
|
|
250
298
|
<div class="panel">
|
|
251
299
|
<h4>Error</h4>
|
|
252
300
|
${errorBlock}
|
|
@@ -257,7 +305,7 @@ const renderTestCard = (test) => {
|
|
|
257
305
|
${artifactMarkup}
|
|
258
306
|
</div>
|
|
259
307
|
</div>
|
|
260
|
-
</
|
|
308
|
+
</details>
|
|
261
309
|
`;
|
|
262
310
|
};
|
|
263
311
|
const renderAdditionalArtifacts = (artifacts) => {
|
|
@@ -318,41 +366,56 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
318
366
|
padding: 40px 20px 80px;
|
|
319
367
|
}
|
|
320
368
|
.hero {
|
|
321
|
-
|
|
369
|
+
position: relative;
|
|
370
|
+
padding: 22px;
|
|
322
371
|
border: 1px solid var(--panel-border);
|
|
323
372
|
border-radius: 24px;
|
|
324
373
|
background: rgba(13, 17, 23, 0.88);
|
|
325
374
|
backdrop-filter: blur(12px);
|
|
326
375
|
}
|
|
376
|
+
.hero-badge {
|
|
377
|
+
position: absolute;
|
|
378
|
+
top: 18px;
|
|
379
|
+
right: 18px;
|
|
380
|
+
display: inline-flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
padding: 6px 10px;
|
|
383
|
+
border-radius: 999px;
|
|
384
|
+
border: 1px solid rgba(125, 211, 252, 0.28);
|
|
385
|
+
background: rgba(125, 211, 252, 0.08);
|
|
386
|
+
color: var(--accent);
|
|
387
|
+
font-size: 11px;
|
|
388
|
+
letter-spacing: 0.06em;
|
|
389
|
+
text-transform: uppercase;
|
|
390
|
+
}
|
|
327
391
|
.eyebrow {
|
|
328
392
|
color: var(--accent);
|
|
329
393
|
text-transform: uppercase;
|
|
330
394
|
letter-spacing: 0.18em;
|
|
331
|
-
font-size:
|
|
332
|
-
margin-bottom:
|
|
395
|
+
font-size: 10px;
|
|
396
|
+
margin-bottom: 10px;
|
|
333
397
|
}
|
|
334
398
|
h1, h2, h3, h4 { margin: 0; }
|
|
335
|
-
h1 { font-size: clamp(
|
|
399
|
+
h1 { font-size: clamp(24px, 4vw, 38px); line-height: 1.05; }
|
|
336
400
|
.hero p {
|
|
337
|
-
margin:
|
|
401
|
+
margin: 10px 0 0;
|
|
338
402
|
color: var(--muted);
|
|
339
|
-
max-width:
|
|
340
|
-
font-size:
|
|
403
|
+
max-width: 640px;
|
|
404
|
+
font-size: 14px;
|
|
341
405
|
line-height: 1.6;
|
|
342
406
|
}
|
|
343
|
-
.summary-grid
|
|
407
|
+
.summary-grid {
|
|
344
408
|
display: grid;
|
|
345
409
|
gap: 16px;
|
|
346
|
-
margin-top:
|
|
410
|
+
margin-top: 18px;
|
|
347
411
|
}
|
|
348
412
|
.summary-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
|
|
349
|
-
.
|
|
350
|
-
.summary-card, .mode-card, .section-shell, .test-card {
|
|
413
|
+
.summary-card, .section-shell, .test-card {
|
|
351
414
|
border: 1px solid var(--panel-border);
|
|
352
415
|
border-radius: 20px;
|
|
353
416
|
background: var(--panel);
|
|
354
417
|
}
|
|
355
|
-
.summary-card
|
|
418
|
+
.summary-card {
|
|
356
419
|
padding: 18px;
|
|
357
420
|
}
|
|
358
421
|
.summary-label {
|
|
@@ -377,27 +440,21 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
377
440
|
color: var(--muted);
|
|
378
441
|
line-height: 1.6;
|
|
379
442
|
}
|
|
380
|
-
.diagram {
|
|
381
|
-
margin-top: 18px;
|
|
382
|
-
padding: 18px;
|
|
383
|
-
border-radius: 16px;
|
|
384
|
-
background: rgba(125, 211, 252, 0.06);
|
|
385
|
-
border: 1px dashed rgba(125, 211, 252, 0.3);
|
|
386
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
387
|
-
white-space: pre-line;
|
|
388
|
-
color: #d7e2f0;
|
|
389
|
-
}
|
|
390
|
-
.mode-card h3 { font-size: 18px; }
|
|
391
|
-
.mode-card p { color: var(--muted); line-height: 1.6; }
|
|
392
443
|
.test-card {
|
|
393
|
-
padding: 20px;
|
|
394
444
|
margin-top: 18px;
|
|
445
|
+
overflow: hidden;
|
|
395
446
|
}
|
|
396
|
-
.test-
|
|
447
|
+
.test-summary {
|
|
397
448
|
display: flex;
|
|
398
449
|
justify-content: space-between;
|
|
399
450
|
gap: 20px;
|
|
400
451
|
align-items: flex-start;
|
|
452
|
+
list-style: none;
|
|
453
|
+
padding: 20px;
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
}
|
|
456
|
+
.test-summary::-webkit-details-marker {
|
|
457
|
+
display: none;
|
|
401
458
|
}
|
|
402
459
|
.status-pill {
|
|
403
460
|
display: inline-flex;
|
|
@@ -417,9 +474,10 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
417
474
|
gap: 6px;
|
|
418
475
|
color: var(--muted);
|
|
419
476
|
font-size: 14px;
|
|
477
|
+
text-align: right;
|
|
420
478
|
}
|
|
421
479
|
.panel {
|
|
422
|
-
margin
|
|
480
|
+
margin: 0 20px 18px;
|
|
423
481
|
padding: 16px;
|
|
424
482
|
background: rgba(13, 17, 23, 0.74);
|
|
425
483
|
border: 1px solid rgba(39, 48, 66, 0.9);
|
|
@@ -480,13 +538,27 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
480
538
|
border-radius: 14px;
|
|
481
539
|
padding: 16px;
|
|
482
540
|
}
|
|
541
|
+
.failed-list-head {
|
|
542
|
+
display: flex;
|
|
543
|
+
justify-content: space-between;
|
|
544
|
+
gap: 12px;
|
|
545
|
+
align-items: center;
|
|
546
|
+
}
|
|
547
|
+
.failed-count {
|
|
548
|
+
color: var(--muted);
|
|
549
|
+
font-size: 14px;
|
|
550
|
+
}
|
|
483
551
|
footer {
|
|
484
552
|
margin-top: 28px;
|
|
485
553
|
color: var(--muted);
|
|
486
554
|
font-size: 14px;
|
|
487
555
|
}
|
|
488
556
|
@media (max-width: 720px) {
|
|
489
|
-
.
|
|
557
|
+
.hero-badge {
|
|
558
|
+
position: static;
|
|
559
|
+
margin-bottom: 12px;
|
|
560
|
+
}
|
|
561
|
+
.test-summary { flex-direction: column; }
|
|
490
562
|
.meta-stack { min-width: 0; }
|
|
491
563
|
}
|
|
492
564
|
</style>
|
|
@@ -494,8 +566,9 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
494
566
|
<body>
|
|
495
567
|
<div class="page">
|
|
496
568
|
<header class="hero">
|
|
569
|
+
<a class="hero-badge" href="${SENTINEL_URL}" target="_blank" rel="noreferrer">Powered by sentinelqa.com</a>
|
|
497
570
|
<div class="eyebrow">Playwright Reporter for CI Debugging</div>
|
|
498
|
-
<h1>
|
|
571
|
+
<h1>Playwright Reporter</h1>
|
|
499
572
|
<p>
|
|
500
573
|
A Playwright reporter that collects traces, screenshots, videos, and logs into
|
|
501
574
|
a single debugging report. Designed to make CI failures easier to diagnose.
|
|
@@ -521,33 +594,10 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
521
594
|
</header>
|
|
522
595
|
|
|
523
596
|
<section class="section-shell">
|
|
524
|
-
<
|
|
525
|
-
|
|
526
|
-
<div class="
|
|
527
|
-
<h3>Local Mode</h3>
|
|
528
|
-
<p>Generates debugging reports locally with traces, screenshots, videos, and logs copied into this report folder.</p>
|
|
529
|
-
</div>
|
|
530
|
-
<div class="mode-card">
|
|
531
|
-
<h3>Cloud Mode</h3>
|
|
532
|
-
<p>Uploads CI runs to Sentinel for team debugging, CI history, shareable links, and AI-generated failure summaries.</p>
|
|
533
|
-
</div>
|
|
597
|
+
<div class="failed-list-head">
|
|
598
|
+
<h2>Failed Tests</h2>
|
|
599
|
+
<div class="failed-count">${failedTests.length} failed</div>
|
|
534
600
|
</div>
|
|
535
|
-
</section>
|
|
536
|
-
|
|
537
|
-
<section class="section-shell">
|
|
538
|
-
<h2>Flow</h2>
|
|
539
|
-
<p>This reporter keeps the setup local-first and only adds Sentinel Cloud when a token is configured.</p>
|
|
540
|
-
<div class="diagram">Playwright CI
|
|
541
|
-
↓
|
|
542
|
-
Sentinel Reporter
|
|
543
|
-
↓
|
|
544
|
-
Artifacts Collected
|
|
545
|
-
↓
|
|
546
|
-
Local Debug Page OR Sentinel Cloud</div>
|
|
547
|
-
</section>
|
|
548
|
-
|
|
549
|
-
<section class="section-shell">
|
|
550
|
-
<h2>Failed Tests</h2>
|
|
551
601
|
${failedTests.length > 0
|
|
552
602
|
? failedTests.map((test) => renderTestCard(test)).join("\n")
|
|
553
603
|
: `<div class="empty-state">No failed tests were found in this run. The local report still includes collected artifacts below.</div>`}
|
|
@@ -559,6 +609,19 @@ Local Debug Page OR Sentinel Cloud</div>
|
|
|
559
609
|
${renderAdditionalArtifacts(extraArtifacts)}
|
|
560
610
|
</section>
|
|
561
611
|
|
|
612
|
+
<section class="section-shell">
|
|
613
|
+
<h2>Optional: Sentinel Cloud</h2>
|
|
614
|
+
<div class="artifact-list">
|
|
615
|
+
<div class="artifact-link">Upload runs to Sentinel Cloud for:</div>
|
|
616
|
+
<div class="artifact-link">• CI history</div>
|
|
617
|
+
<div class="artifact-link">• shareable run links</div>
|
|
618
|
+
<div class="artifact-link">• AI failure summaries</div>
|
|
619
|
+
<div class="artifact-link">
|
|
620
|
+
<a href="${SENTINEL_URL}" target="_blank" rel="noreferrer">More on sentinelqa.com</a>
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
</section>
|
|
624
|
+
|
|
562
625
|
<footer>
|
|
563
626
|
Generated by <a href="${SENTINEL_URL}" target="_blank" rel="noreferrer">Sentinel Playwright Reporter</a>.
|
|
564
627
|
</footer>
|
|
@@ -586,7 +649,8 @@ function generateLocalDebugReport(options) {
|
|
|
586
649
|
]);
|
|
587
650
|
const reportJsonRaw = fs_1.default.readFileSync(options.playwrightJsonPath, "utf8");
|
|
588
651
|
const reportJson = JSON.parse(reportJsonRaw);
|
|
589
|
-
const
|
|
652
|
+
const reportRoot = { suites: reportJson?.suites || [] };
|
|
653
|
+
const tests = collectTests(reportRoot);
|
|
590
654
|
const testsById = new Map(tests.map((test) => [test.id, test]));
|
|
591
655
|
const claimedSourcePaths = new Set();
|
|
592
656
|
const attachArtifactToTest = (sourcePath, testId) => {
|
|
@@ -600,34 +664,17 @@ function generateLocalDebugReport(options) {
|
|
|
600
664
|
return;
|
|
601
665
|
}
|
|
602
666
|
};
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
for (const
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
titlePath.join(" > ")
|
|
612
|
-
].join("::");
|
|
613
|
-
const resultList = Array.isArray(test.results) ? test.results : [];
|
|
614
|
-
for (const result of resultList) {
|
|
615
|
-
const attachments = Array.isArray(result?.attachments) ? result.attachments : [];
|
|
616
|
-
for (const attachment of attachments) {
|
|
617
|
-
const resolvedPath = resolveExistingFile(attachment?.path, baseDirs);
|
|
618
|
-
if (!resolvedPath)
|
|
619
|
-
continue;
|
|
620
|
-
attachArtifactToTest(resolvedPath, testId);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
667
|
+
for (const testRef of collectTestRefs(reportRoot)) {
|
|
668
|
+
for (const result of testRef.resultList) {
|
|
669
|
+
const attachments = Array.isArray(result?.attachments) ? result.attachments : [];
|
|
670
|
+
for (const attachment of attachments) {
|
|
671
|
+
const resolvedPath = resolveExistingFile(attachment?.path, baseDirs);
|
|
672
|
+
if (!resolvedPath)
|
|
673
|
+
continue;
|
|
674
|
+
attachArtifactToTest(resolvedPath, testRef.id);
|
|
623
675
|
}
|
|
624
676
|
}
|
|
625
|
-
|
|
626
|
-
for (const suite of node.suites)
|
|
627
|
-
walkSuites(suite, nextTitles);
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
walkSuites({ suites: reportJson?.suites || [] });
|
|
677
|
+
}
|
|
631
678
|
const extraArtifacts = [];
|
|
632
679
|
for (const sourceDir of sourceDirs) {
|
|
633
680
|
for (const filePath of listFilesRecursive(sourceDir)) {
|