@sentinelqa/playwright-reporter 0.1.17 → 0.1.18
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 +352 -22
- package/dist/reporter.js +31 -13
- package/package.json +1 -1
package/dist/localReport.js
CHANGED
|
@@ -37,6 +37,51 @@ const escapeHtml = (value) => value
|
|
|
37
37
|
.replace(/>/g, ">")
|
|
38
38
|
.replace(/"/g, """)
|
|
39
39
|
.replace(/'/g, "'");
|
|
40
|
+
const ansiToHtml = (value) => {
|
|
41
|
+
const parts = value.split(/(\u001b\[[0-9;]*m)/g);
|
|
42
|
+
const html = [];
|
|
43
|
+
const openTags = [];
|
|
44
|
+
const closeAll = () => {
|
|
45
|
+
while (openTags.length > 0) {
|
|
46
|
+
html.push("</span>");
|
|
47
|
+
openTags.pop();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
for (const part of parts) {
|
|
51
|
+
const match = part.match(/^\u001b\[([0-9;]*)m$/);
|
|
52
|
+
if (!match) {
|
|
53
|
+
html.push(escapeHtml(part));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const codes = match[1]
|
|
57
|
+
.split(";")
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.map((entry) => Number.parseInt(entry, 10));
|
|
60
|
+
if (codes.length === 0 || codes.includes(0)) {
|
|
61
|
+
closeAll();
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const code of codes) {
|
|
65
|
+
const className = code === 1
|
|
66
|
+
? "ansi-bold"
|
|
67
|
+
: code === 31
|
|
68
|
+
? "ansi-red"
|
|
69
|
+
: code === 32
|
|
70
|
+
? "ansi-green"
|
|
71
|
+
: code === 33
|
|
72
|
+
? "ansi-yellow"
|
|
73
|
+
: code === 36
|
|
74
|
+
? "ansi-cyan"
|
|
75
|
+
: null;
|
|
76
|
+
if (!className)
|
|
77
|
+
continue;
|
|
78
|
+
html.push(`<span class="${className}">`);
|
|
79
|
+
openTags.push(className);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
closeAll();
|
|
83
|
+
return html.join("");
|
|
84
|
+
};
|
|
40
85
|
const ensureDir = (dirPath) => {
|
|
41
86
|
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
42
87
|
};
|
|
@@ -254,9 +299,22 @@ const renderArtifact = (artifact) => {
|
|
|
254
299
|
const label = escapeHtml(artifact.label);
|
|
255
300
|
if (artifact.kind === "trace") {
|
|
256
301
|
return `
|
|
257
|
-
<div class="artifact-link">
|
|
258
|
-
<
|
|
259
|
-
|
|
302
|
+
<div class="artifact-link artifact-link-trace">
|
|
303
|
+
<div class="artifact-trace-row">
|
|
304
|
+
<div class="artifact-trace-meta">
|
|
305
|
+
<span class="artifact-kind">Trace</span>
|
|
306
|
+
<a href="${href}" target="_blank" rel="noreferrer">${label}</a>
|
|
307
|
+
</div>
|
|
308
|
+
<a
|
|
309
|
+
class="trace-button"
|
|
310
|
+
href="${href}"
|
|
311
|
+
target="_blank"
|
|
312
|
+
rel="noreferrer"
|
|
313
|
+
data-trace-path="${href}"
|
|
314
|
+
>
|
|
315
|
+
View Trace
|
|
316
|
+
</a>
|
|
317
|
+
</div>
|
|
260
318
|
</div>
|
|
261
319
|
`;
|
|
262
320
|
}
|
|
@@ -267,7 +325,7 @@ const renderArtifact = (artifact) => {
|
|
|
267
325
|
<span class="artifact-kind">Screenshot</span>
|
|
268
326
|
<a href="${href}" target="_blank" rel="noreferrer">${label}</a>
|
|
269
327
|
</div>
|
|
270
|
-
<img src="${href}" alt="${label}" loading="lazy" />
|
|
328
|
+
<img src="${href}" alt="${label}" loading="lazy" data-preview-image="${href}" />
|
|
271
329
|
</div>
|
|
272
330
|
`;
|
|
273
331
|
}
|
|
@@ -289,6 +347,42 @@ const renderArtifact = (artifact) => {
|
|
|
289
347
|
</div>
|
|
290
348
|
`;
|
|
291
349
|
};
|
|
350
|
+
const renderArtifactGroups = (artifacts) => {
|
|
351
|
+
if (artifacts.length === 0) {
|
|
352
|
+
return `<div class="empty-state">No test-linked artifacts were detected for this result.</div>`;
|
|
353
|
+
}
|
|
354
|
+
const groups = [
|
|
355
|
+
{
|
|
356
|
+
title: "Screenshots",
|
|
357
|
+
items: artifacts.filter((artifact) => artifact.kind === "screenshot")
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
title: "Videos",
|
|
361
|
+
items: artifacts.filter((artifact) => artifact.kind === "video")
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
title: "Traces",
|
|
365
|
+
items: artifacts.filter((artifact) => artifact.kind === "trace")
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
title: "Other files",
|
|
369
|
+
items: artifacts.filter((artifact) => !["screenshot", "video", "trace"].includes(artifact.kind))
|
|
370
|
+
}
|
|
371
|
+
].filter((group) => group.items.length > 0);
|
|
372
|
+
return groups
|
|
373
|
+
.map((group) => `
|
|
374
|
+
<details class="artifact-group" ${group.title === "Screenshots" ? "open" : ""}>
|
|
375
|
+
<summary class="artifact-group-summary">
|
|
376
|
+
<span>${escapeHtml(group.title)}</span>
|
|
377
|
+
<span class="artifact-group-count">(${group.items.length})</span>
|
|
378
|
+
</summary>
|
|
379
|
+
<div class="artifact-grid">
|
|
380
|
+
${group.items.map((artifact) => renderArtifact(artifact)).join("\n")}
|
|
381
|
+
</div>
|
|
382
|
+
</details>
|
|
383
|
+
`)
|
|
384
|
+
.join("\n");
|
|
385
|
+
};
|
|
292
386
|
const renderTestCard = (test) => {
|
|
293
387
|
const statusClass = test.status === "passed" ? "status-passed" : "status-failed";
|
|
294
388
|
const fileLine = test.file ? `<div class="meta-item">${escapeHtml(test.file)}</div>` : "";
|
|
@@ -296,11 +390,25 @@ const renderTestCard = (test) => {
|
|
|
296
390
|
? `<div class="meta-item">Project: ${escapeHtml(test.projectName)}</div>`
|
|
297
391
|
: "";
|
|
298
392
|
const errorBlock = test.errors.length > 0
|
|
299
|
-
?
|
|
393
|
+
? (() => {
|
|
394
|
+
const rawError = escapeHtml(test.errors.join("\n\n"));
|
|
395
|
+
return `<div class="error-block" data-collapsed="true">
|
|
396
|
+
<div class="error-actions">
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
class="copy-button"
|
|
400
|
+
data-copy-error="${rawError}"
|
|
401
|
+
aria-label="Copy error"
|
|
402
|
+
>
|
|
403
|
+
Copy
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
<pre class="error-preview">${ansiToHtml(test.errors.join("\n\n"))}</pre>
|
|
407
|
+
<button type="button" class="expand-button" data-expand-error>Expand full error</button>
|
|
408
|
+
</div>`;
|
|
409
|
+
})()
|
|
300
410
|
: `<pre>No error message was attached to this result.</pre>`;
|
|
301
|
-
const artifactMarkup = test.artifacts
|
|
302
|
-
? test.artifacts.map((artifact) => renderArtifact(artifact)).join("\n")
|
|
303
|
-
: `<div class="empty-state">No test-linked artifacts were detected for this result.</div>`;
|
|
411
|
+
const artifactMarkup = renderArtifactGroups(test.artifacts);
|
|
304
412
|
return `
|
|
305
413
|
<details class="test-card">
|
|
306
414
|
<summary class="test-summary">
|
|
@@ -331,20 +439,7 @@ const renderAdditionalArtifacts = (artifacts) => {
|
|
|
331
439
|
if (artifacts.length === 0) {
|
|
332
440
|
return "";
|
|
333
441
|
}
|
|
334
|
-
return
|
|
335
|
-
<div class="artifact-list">
|
|
336
|
-
${artifacts
|
|
337
|
-
.map((artifact) => `
|
|
338
|
-
<div class="artifact-link">
|
|
339
|
-
<span class="artifact-kind">${escapeHtml(artifact.kind)}</span>
|
|
340
|
-
<a href="${escapeHtml(artifact.relativePath)}" target="_blank" rel="noreferrer">
|
|
341
|
-
${escapeHtml(artifact.label)}
|
|
342
|
-
</a>
|
|
343
|
-
</div>
|
|
344
|
-
`)
|
|
345
|
-
.join("\n")}
|
|
346
|
-
</div>
|
|
347
|
-
`;
|
|
442
|
+
return renderArtifactGroups(artifacts);
|
|
348
443
|
};
|
|
349
444
|
const tryMapRemainingArtifactsToTests = (tests, artifactPaths, reportDir, usedRelativePaths, claimedSourcePaths) => {
|
|
350
445
|
const candidateTests = tests.filter((test) => ["failed", "timedOut", "interrupted"].includes(test.status));
|
|
@@ -531,6 +626,45 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
531
626
|
font-size: 13px;
|
|
532
627
|
color: #d5dde8;
|
|
533
628
|
}
|
|
629
|
+
.ansi-bold { font-weight: 700; }
|
|
630
|
+
.ansi-red { color: #fb7185; }
|
|
631
|
+
.ansi-green { color: #4ade80; }
|
|
632
|
+
.ansi-yellow { color: #facc15; }
|
|
633
|
+
.ansi-cyan { color: #67e8f9; }
|
|
634
|
+
.error-block[data-collapsed="true"] .error-preview {
|
|
635
|
+
max-height: 180px;
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
position: relative;
|
|
638
|
+
}
|
|
639
|
+
.error-actions {
|
|
640
|
+
display: flex;
|
|
641
|
+
justify-content: flex-end;
|
|
642
|
+
}
|
|
643
|
+
.error-block[data-collapsed="true"] .error-preview::after {
|
|
644
|
+
content: "";
|
|
645
|
+
position: absolute;
|
|
646
|
+
inset: auto 0 0 0;
|
|
647
|
+
height: 56px;
|
|
648
|
+
background: linear-gradient(180deg, rgba(13, 17, 23, 0), rgba(13, 17, 23, 1));
|
|
649
|
+
}
|
|
650
|
+
.copy-button,
|
|
651
|
+
.expand-button {
|
|
652
|
+
margin-top: 12px;
|
|
653
|
+
border: 1px solid rgba(125, 211, 252, 0.28);
|
|
654
|
+
background: rgba(125, 211, 252, 0.08);
|
|
655
|
+
color: var(--accent);
|
|
656
|
+
border-radius: 999px;
|
|
657
|
+
padding: 8px 12px;
|
|
658
|
+
font-size: 12px;
|
|
659
|
+
font-weight: 600;
|
|
660
|
+
text-transform: uppercase;
|
|
661
|
+
letter-spacing: 0.06em;
|
|
662
|
+
cursor: pointer;
|
|
663
|
+
}
|
|
664
|
+
.copy-button {
|
|
665
|
+
margin-top: 0;
|
|
666
|
+
margin-left: auto;
|
|
667
|
+
}
|
|
534
668
|
.artifact-grid {
|
|
535
669
|
display: grid;
|
|
536
670
|
gap: 14px;
|
|
@@ -543,11 +677,15 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
543
677
|
background: rgba(9, 13, 20, 0.9);
|
|
544
678
|
padding: 12px;
|
|
545
679
|
}
|
|
680
|
+
.artifact-link-trace {
|
|
681
|
+
padding: 14px;
|
|
682
|
+
}
|
|
546
683
|
.artifact-card img, .artifact-card video {
|
|
547
684
|
width: 100%;
|
|
548
685
|
border-radius: 10px;
|
|
549
686
|
margin-top: 12px;
|
|
550
687
|
background: #05070b;
|
|
688
|
+
cursor: zoom-in;
|
|
551
689
|
}
|
|
552
690
|
.artifact-meta {
|
|
553
691
|
display: flex;
|
|
@@ -567,11 +705,70 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
567
705
|
text-transform: uppercase;
|
|
568
706
|
letter-spacing: 0.08em;
|
|
569
707
|
}
|
|
708
|
+
.artifact-trace-row {
|
|
709
|
+
display: flex;
|
|
710
|
+
justify-content: space-between;
|
|
711
|
+
gap: 12px;
|
|
712
|
+
align-items: center;
|
|
713
|
+
}
|
|
714
|
+
.artifact-trace-meta {
|
|
715
|
+
display: flex;
|
|
716
|
+
gap: 10px;
|
|
717
|
+
align-items: center;
|
|
718
|
+
flex-wrap: wrap;
|
|
719
|
+
}
|
|
720
|
+
.trace-button {
|
|
721
|
+
display: inline-flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
justify-content: center;
|
|
724
|
+
padding: 8px 12px;
|
|
725
|
+
border-radius: 999px;
|
|
726
|
+
border: 1px solid rgba(125, 211, 252, 0.28);
|
|
727
|
+
background: rgba(125, 211, 252, 0.08);
|
|
728
|
+
color: var(--accent);
|
|
729
|
+
font-size: 12px;
|
|
730
|
+
font-weight: 600;
|
|
731
|
+
text-transform: uppercase;
|
|
732
|
+
letter-spacing: 0.06em;
|
|
733
|
+
white-space: nowrap;
|
|
734
|
+
}
|
|
735
|
+
.trace-button:hover {
|
|
736
|
+
text-decoration: none;
|
|
737
|
+
background: rgba(125, 211, 252, 0.14);
|
|
738
|
+
}
|
|
570
739
|
.artifact-list {
|
|
571
740
|
display: grid;
|
|
572
741
|
gap: 12px;
|
|
573
742
|
margin-top: 16px;
|
|
574
743
|
}
|
|
744
|
+
.artifact-group {
|
|
745
|
+
margin-top: 12px;
|
|
746
|
+
border: 1px solid rgba(39, 48, 66, 0.9);
|
|
747
|
+
border-radius: 14px;
|
|
748
|
+
background: rgba(9, 13, 20, 0.42);
|
|
749
|
+
overflow: hidden;
|
|
750
|
+
}
|
|
751
|
+
.artifact-group-summary {
|
|
752
|
+
display: flex;
|
|
753
|
+
justify-content: space-between;
|
|
754
|
+
align-items: center;
|
|
755
|
+
gap: 12px;
|
|
756
|
+
padding: 14px 16px;
|
|
757
|
+
cursor: pointer;
|
|
758
|
+
list-style: none;
|
|
759
|
+
font-weight: 600;
|
|
760
|
+
}
|
|
761
|
+
.artifact-group-summary::-webkit-details-marker {
|
|
762
|
+
display: none;
|
|
763
|
+
}
|
|
764
|
+
.artifact-group-count {
|
|
765
|
+
color: var(--muted);
|
|
766
|
+
font-weight: 500;
|
|
767
|
+
}
|
|
768
|
+
.artifact-group .artifact-grid {
|
|
769
|
+
padding: 0 16px 16px;
|
|
770
|
+
margin-top: 0;
|
|
771
|
+
}
|
|
575
772
|
.section-shell ul {
|
|
576
773
|
margin: 12px 0 0 18px;
|
|
577
774
|
color: var(--text);
|
|
@@ -600,6 +797,45 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
600
797
|
color: var(--muted);
|
|
601
798
|
font-size: 14px;
|
|
602
799
|
}
|
|
800
|
+
.preview-overlay {
|
|
801
|
+
position: fixed;
|
|
802
|
+
inset: 0;
|
|
803
|
+
background: rgba(4, 8, 14, 0.88);
|
|
804
|
+
display: none;
|
|
805
|
+
align-items: center;
|
|
806
|
+
justify-content: center;
|
|
807
|
+
padding: 32px;
|
|
808
|
+
z-index: 999;
|
|
809
|
+
}
|
|
810
|
+
.preview-overlay.is-open {
|
|
811
|
+
display: flex;
|
|
812
|
+
}
|
|
813
|
+
.preview-shell {
|
|
814
|
+
max-width: min(1200px, 96vw);
|
|
815
|
+
max-height: 92vh;
|
|
816
|
+
position: relative;
|
|
817
|
+
}
|
|
818
|
+
.preview-shell img {
|
|
819
|
+
display: block;
|
|
820
|
+
max-width: 100%;
|
|
821
|
+
max-height: 92vh;
|
|
822
|
+
border-radius: 16px;
|
|
823
|
+
border: 1px solid rgba(39, 48, 66, 0.9);
|
|
824
|
+
box-shadow: 0 20px 80px rgba(0, 0, 0, 0.5);
|
|
825
|
+
}
|
|
826
|
+
.preview-close {
|
|
827
|
+
position: absolute;
|
|
828
|
+
top: 12px;
|
|
829
|
+
right: 12px;
|
|
830
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
831
|
+
background: rgba(9, 13, 20, 0.75);
|
|
832
|
+
color: #fff;
|
|
833
|
+
border-radius: 999px;
|
|
834
|
+
width: 40px;
|
|
835
|
+
height: 40px;
|
|
836
|
+
cursor: pointer;
|
|
837
|
+
font-size: 18px;
|
|
838
|
+
}
|
|
603
839
|
@media (max-width: 720px) {
|
|
604
840
|
.hero-badge {
|
|
605
841
|
position: static;
|
|
@@ -607,6 +843,10 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
607
843
|
}
|
|
608
844
|
.test-summary { flex-direction: column; }
|
|
609
845
|
.meta-stack { min-width: 0; }
|
|
846
|
+
.artifact-trace-row {
|
|
847
|
+
flex-direction: column;
|
|
848
|
+
align-items: flex-start;
|
|
849
|
+
}
|
|
610
850
|
}
|
|
611
851
|
</style>
|
|
612
852
|
</head>
|
|
@@ -675,6 +915,96 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
675
915
|
Generated by <a href="${SENTINEL_URL}" target="_blank" rel="noreferrer">Sentinel Playwright Reporter</a>.
|
|
676
916
|
</footer>
|
|
677
917
|
</div>
|
|
918
|
+
<div class="preview-overlay" id="preview-overlay" aria-hidden="true">
|
|
919
|
+
<div class="preview-shell">
|
|
920
|
+
<button type="button" class="preview-close" id="preview-close" aria-label="Close preview">×</button>
|
|
921
|
+
<img id="preview-image" alt="Screenshot preview" />
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
<script>
|
|
925
|
+
(function () {
|
|
926
|
+
document.querySelectorAll("[data-trace-path]").forEach(function (button) {
|
|
927
|
+
var tracePath = button.getAttribute("data-trace-path");
|
|
928
|
+
if (!tracePath) return;
|
|
929
|
+
try {
|
|
930
|
+
if (window.location.protocol === "http:" || window.location.protocol === "https:") {
|
|
931
|
+
var traceUrl = new URL(tracePath, window.location.href).href;
|
|
932
|
+
button.setAttribute(
|
|
933
|
+
"href",
|
|
934
|
+
"https://trace.playwright.dev/?trace=" + encodeURIComponent(traceUrl)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
} catch (_error) {
|
|
938
|
+
// Keep the raw trace file link as fallback.
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
document.querySelectorAll("[data-expand-error]").forEach(function (button) {
|
|
943
|
+
button.addEventListener("click", function () {
|
|
944
|
+
var block = button.closest(".error-block");
|
|
945
|
+
if (!block) return;
|
|
946
|
+
var isCollapsed = block.getAttribute("data-collapsed") !== "false";
|
|
947
|
+
block.setAttribute("data-collapsed", isCollapsed ? "false" : "true");
|
|
948
|
+
button.textContent = isCollapsed ? "Collapse error" : "Expand full error";
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
document.querySelectorAll("[data-copy-error]").forEach(function (button) {
|
|
953
|
+
button.addEventListener("click", async function () {
|
|
954
|
+
var rawError = button.getAttribute("data-copy-error");
|
|
955
|
+
if (!rawError) return;
|
|
956
|
+
var text = rawError
|
|
957
|
+
.replace(/"/g, '"')
|
|
958
|
+
.replace(/'/g, "'")
|
|
959
|
+
.replace(/</g, "<")
|
|
960
|
+
.replace(/>/g, ">")
|
|
961
|
+
.replace(/&/g, "&");
|
|
962
|
+
try {
|
|
963
|
+
await navigator.clipboard.writeText(text);
|
|
964
|
+
var previousText = button.textContent;
|
|
965
|
+
button.textContent = "Copied";
|
|
966
|
+
setTimeout(function () {
|
|
967
|
+
button.textContent = previousText || "Copy";
|
|
968
|
+
}, 1200);
|
|
969
|
+
} catch (_error) {
|
|
970
|
+
button.textContent = "Copy failed";
|
|
971
|
+
setTimeout(function () {
|
|
972
|
+
button.textContent = "Copy";
|
|
973
|
+
}, 1200);
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
var overlay = document.getElementById("preview-overlay");
|
|
979
|
+
var previewImage = document.getElementById("preview-image");
|
|
980
|
+
var previewClose = document.getElementById("preview-close");
|
|
981
|
+
if (overlay && previewImage && previewClose) {
|
|
982
|
+
var closePreview = function () {
|
|
983
|
+
overlay.classList.remove("is-open");
|
|
984
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
985
|
+
previewImage.removeAttribute("src");
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
document.querySelectorAll("[data-preview-image]").forEach(function (image) {
|
|
989
|
+
image.addEventListener("click", function () {
|
|
990
|
+
var src = image.getAttribute("data-preview-image");
|
|
991
|
+
if (!src) return;
|
|
992
|
+
previewImage.setAttribute("src", src);
|
|
993
|
+
overlay.classList.add("is-open");
|
|
994
|
+
overlay.setAttribute("aria-hidden", "false");
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
previewClose.addEventListener("click", closePreview);
|
|
999
|
+
overlay.addEventListener("click", function (event) {
|
|
1000
|
+
if (event.target === overlay) closePreview();
|
|
1001
|
+
});
|
|
1002
|
+
window.addEventListener("keydown", function (event) {
|
|
1003
|
+
if (event.key === "Escape") closePreview();
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
})();
|
|
1007
|
+
</script>
|
|
678
1008
|
</body>
|
|
679
1009
|
</html>`;
|
|
680
1010
|
};
|
package/dist/reporter.js
CHANGED
|
@@ -15,6 +15,17 @@ const formatTerminalLink = (label, target) => {
|
|
|
15
15
|
return label;
|
|
16
16
|
return `\u001B]8;;${target}\u0007${label}\u001B]8;;\u0007`;
|
|
17
17
|
};
|
|
18
|
+
const colorize = (value, code) => {
|
|
19
|
+
if (!process.stdout.isTTY)
|
|
20
|
+
return value;
|
|
21
|
+
return `\u001b[${code}m${value}\u001b[0m`;
|
|
22
|
+
};
|
|
23
|
+
const bold = (value) => colorize(value, "1");
|
|
24
|
+
const green = (value) => colorize(value, "32");
|
|
25
|
+
const cyan = (value) => colorize(value, "36");
|
|
26
|
+
const yellow = (value) => colorize(value, "33");
|
|
27
|
+
const dim = (value) => colorize(value, "2");
|
|
28
|
+
const magenta = (value) => colorize(value, "35");
|
|
18
29
|
class SentinelReporter {
|
|
19
30
|
constructor(options) {
|
|
20
31
|
this.failedCount = 0;
|
|
@@ -46,18 +57,32 @@ class SentinelReporter {
|
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
printLocalReport(localReportPath) {
|
|
49
|
-
console.log("");
|
|
50
|
-
console.log("✔ Artifacts collected");
|
|
51
|
-
console.log("✔ Sentinel debugging report generated");
|
|
52
|
-
console.log("");
|
|
53
|
-
console.log("Report location:");
|
|
54
60
|
const relativeReportPath = path_1.default
|
|
55
61
|
.relative(process.cwd(), localReportPath)
|
|
56
62
|
.replace(/\\/g, "/");
|
|
57
63
|
const displayPath = relativeReportPath.startsWith(".")
|
|
58
64
|
? relativeReportPath
|
|
59
65
|
: `./${relativeReportPath}`;
|
|
60
|
-
|
|
66
|
+
const openCommand = `open ${displayPath}`;
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log(green("✔ Artifacts collected"));
|
|
69
|
+
console.log(green("✔ Sentinel HTML debugging report created"));
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log(bold("Report"));
|
|
72
|
+
console.log(` ${cyan(formatTerminalLink(displayPath, (0, url_1.pathToFileURL)(localReportPath).href))}`);
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log(bold("Open"));
|
|
75
|
+
console.log(` ${cyan(openCommand)}`);
|
|
76
|
+
console.log("");
|
|
77
|
+
console.log(yellow("Tip"));
|
|
78
|
+
console.log(` ${dim("Upload runs to Sentinel Cloud for CI history,")}`);
|
|
79
|
+
console.log(` ${dim("shareable debugging links, and AI summaries.")}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log(` ${cyan(formatTerminalLink("https://sentinelqa.com", "https://sentinelqa.com"))}`);
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(` ${magenta("★ If this reporter helped you debug faster,")}`);
|
|
84
|
+
console.log(` ${dim("consider starring the project:")}`);
|
|
85
|
+
console.log(` ${cyan(formatTerminalLink("https://github.com/sentinelqa/playwright-reporter", "https://github.com/sentinelqa/playwright-reporter"))}`);
|
|
61
86
|
}
|
|
62
87
|
async onEnd() {
|
|
63
88
|
const hasSentinelToken = Boolean(process.env.SENTINEL_TOKEN);
|
|
@@ -76,13 +101,6 @@ class SentinelReporter {
|
|
|
76
101
|
this.printLocalReport(localReport.htmlPath);
|
|
77
102
|
console.log("");
|
|
78
103
|
if (!hasSentinelToken) {
|
|
79
|
-
console.log("Optional:");
|
|
80
|
-
console.log("Upload runs to Sentinel Cloud for:");
|
|
81
|
-
console.log("• CI history");
|
|
82
|
-
console.log("• shareable run links");
|
|
83
|
-
console.log("• AI failure summaries");
|
|
84
|
-
console.log("");
|
|
85
|
-
console.log(`Learn more: ${formatTerminalLink("https://sentinelqa.com", "https://sentinelqa.com")}`);
|
|
86
104
|
return;
|
|
87
105
|
}
|
|
88
106
|
console.log("Sentinel upload skipped for this local run.");
|