executable-stories-formatters 0.2.0 → 0.4.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/README.md +1 -1
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +401 -7
- package/dist/cli.js.map +1 -1
- package/dist/{index-DCJ0NvAp.d.cts → index-DyeUWfYK.d.cts} +26 -1
- package/dist/{index-DCJ0NvAp.d.ts → index-DyeUWfYK.d.ts} +26 -1
- package/dist/index.cjs +433 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +430 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/README.md +7 -12
package/dist/index.cjs
CHANGED
|
@@ -48,7 +48,7 @@ __export(src_exports, {
|
|
|
48
48
|
deriveStepResults: () => deriveStepResults,
|
|
49
49
|
detectCI: () => detectCI4,
|
|
50
50
|
findGitDir: () => findGitDir,
|
|
51
|
-
formatDuration: () =>
|
|
51
|
+
formatDuration: () => formatDuration2,
|
|
52
52
|
generateRunId: () => generateRunId,
|
|
53
53
|
generateTestCaseId: () => generateTestCaseId,
|
|
54
54
|
mergeStepResults: () => mergeStepResults,
|
|
@@ -65,7 +65,9 @@ __export(src_exports, {
|
|
|
65
65
|
readPackageVersion: () => readPackageVersion,
|
|
66
66
|
resolveAttachment: () => resolveAttachment,
|
|
67
67
|
resolveAttachments: () => resolveAttachments,
|
|
68
|
+
resolveTraceUrl: () => resolveTraceUrl,
|
|
68
69
|
slugify: () => slugify,
|
|
70
|
+
tryGetActiveOtelContext: () => tryGetActiveOtelContext,
|
|
69
71
|
validateCanonicalRun: () => validateCanonicalRun
|
|
70
72
|
});
|
|
71
73
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -953,20 +955,32 @@ function initCollapse() {
|
|
|
953
955
|
}
|
|
954
956
|
});
|
|
955
957
|
});
|
|
958
|
+
|
|
959
|
+
document.querySelectorAll('.trace-view-header').forEach(header => {
|
|
960
|
+
header.addEventListener('click', () => {
|
|
961
|
+
toggleCollapse(header, header.closest('.trace-view'));
|
|
962
|
+
});
|
|
963
|
+
header.addEventListener('keydown', (e) => {
|
|
964
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
965
|
+
e.preventDefault();
|
|
966
|
+
toggleCollapse(header, header.closest('.trace-view'));
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
});
|
|
956
970
|
}
|
|
957
971
|
|
|
958
972
|
function expandAll() {
|
|
959
|
-
document.querySelectorAll('.feature, .scenario').forEach(el => {
|
|
973
|
+
document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
|
|
960
974
|
el.classList.remove('collapsed');
|
|
961
|
-
const header = el.querySelector('.feature-header, .scenario-header');
|
|
975
|
+
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
962
976
|
header?.setAttribute('aria-expanded', 'true');
|
|
963
977
|
});
|
|
964
978
|
}
|
|
965
979
|
|
|
966
980
|
function collapseAll() {
|
|
967
|
-
document.querySelectorAll('.feature, .scenario').forEach(el => {
|
|
981
|
+
document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
|
|
968
982
|
el.classList.add('collapsed');
|
|
969
|
-
const header = el.querySelector('.feature-header, .scenario-header');
|
|
983
|
+
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
970
984
|
header?.setAttribute('aria-expanded', 'false');
|
|
971
985
|
});
|
|
972
986
|
}
|
|
@@ -1156,6 +1170,7 @@ var CSS_STYLES = `
|
|
|
1156
1170
|
--tag-bg: hsl(145 55% 95%);
|
|
1157
1171
|
--tag-color: hsl(145 63% 30%);
|
|
1158
1172
|
--tag-border: hsl(145 55% 85%);
|
|
1173
|
+
--step-param-color: hsl(220 70% 50%);
|
|
1159
1174
|
|
|
1160
1175
|
/* Accordion/Collapsible styling */
|
|
1161
1176
|
--accordion-header-hover: hsl(0 0% 98%);
|
|
@@ -1214,6 +1229,7 @@ var CSS_STYLES = `
|
|
|
1214
1229
|
--tag-bg: hsl(145 35% 14%);
|
|
1215
1230
|
--tag-color: hsl(145 63% 60%);
|
|
1216
1231
|
--tag-border: hsl(145 35% 22%);
|
|
1232
|
+
--step-param-color: hsl(220 70% 70%);
|
|
1217
1233
|
|
|
1218
1234
|
/* Accordion/Collapsible styling */
|
|
1219
1235
|
--accordion-header-hover: hsl(0 0% 11%);
|
|
@@ -1262,6 +1278,7 @@ var CSS_STYLES = `
|
|
|
1262
1278
|
--tag-bg: hsl(145 35% 14%);
|
|
1263
1279
|
--tag-color: hsl(145 63% 60%);
|
|
1264
1280
|
--tag-border: hsl(145 35% 22%);
|
|
1281
|
+
--step-param-color: hsl(220 70% 70%);
|
|
1265
1282
|
--accordion-header-hover: hsl(0 0% 11%);
|
|
1266
1283
|
--accordion-content-bg: hsl(0 0% 7%);
|
|
1267
1284
|
}
|
|
@@ -1807,6 +1824,12 @@ body {
|
|
|
1807
1824
|
color: var(--foreground);
|
|
1808
1825
|
}
|
|
1809
1826
|
|
|
1827
|
+
.step-param {
|
|
1828
|
+
font-style: italic;
|
|
1829
|
+
font-weight: 500;
|
|
1830
|
+
color: var(--step-param-color);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1810
1833
|
.step-duration {
|
|
1811
1834
|
color: var(--muted-foreground);
|
|
1812
1835
|
font-size: 0.6875rem;
|
|
@@ -2483,6 +2506,133 @@ body {
|
|
|
2483
2506
|
font-family: inherit;
|
|
2484
2507
|
background: none;
|
|
2485
2508
|
}
|
|
2509
|
+
|
|
2510
|
+
/* ============================================================================
|
|
2511
|
+
Trace View - OTel span waterfall
|
|
2512
|
+
============================================================================ */
|
|
2513
|
+
.trace-view {
|
|
2514
|
+
margin-top: 0.75rem;
|
|
2515
|
+
border: 1px solid var(--border);
|
|
2516
|
+
border-radius: calc(var(--radius) - 2px);
|
|
2517
|
+
overflow: hidden;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
.trace-view-header {
|
|
2521
|
+
display: flex;
|
|
2522
|
+
align-items: center;
|
|
2523
|
+
gap: 0.5rem;
|
|
2524
|
+
padding: 0.5rem 0.75rem;
|
|
2525
|
+
background: var(--card);
|
|
2526
|
+
cursor: pointer;
|
|
2527
|
+
user-select: none;
|
|
2528
|
+
font-size: 0.8125rem;
|
|
2529
|
+
font-weight: 500;
|
|
2530
|
+
color: var(--foreground);
|
|
2531
|
+
transition: background-color 0.15s ease;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
.trace-view-header:hover {
|
|
2535
|
+
background: var(--accordion-header-hover);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
.trace-view-count {
|
|
2539
|
+
font-size: 0.6875rem;
|
|
2540
|
+
font-weight: 500;
|
|
2541
|
+
padding: 0.125rem 0.5rem;
|
|
2542
|
+
background: var(--success-light);
|
|
2543
|
+
color: var(--success);
|
|
2544
|
+
border: 1px solid var(--success-border);
|
|
2545
|
+
border-radius: 9999px;
|
|
2546
|
+
font-family: var(--font-mono);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
.trace-view-content {
|
|
2550
|
+
border-top: 1px solid var(--border);
|
|
2551
|
+
padding: 0.5rem 0.75rem;
|
|
2552
|
+
background: var(--accordion-content-bg);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
.trace-view.collapsed .trace-view-content {
|
|
2556
|
+
display: none;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.trace-view-axis {
|
|
2560
|
+
display: flex;
|
|
2561
|
+
justify-content: space-between;
|
|
2562
|
+
font-size: 0.625rem;
|
|
2563
|
+
font-family: var(--font-mono);
|
|
2564
|
+
color: var(--muted-foreground);
|
|
2565
|
+
padding-bottom: 0.375rem;
|
|
2566
|
+
margin-bottom: 0.375rem;
|
|
2567
|
+
border-bottom: 1px solid var(--border);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.trace-view-row {
|
|
2571
|
+
display: flex;
|
|
2572
|
+
align-items: center;
|
|
2573
|
+
gap: 0.5rem;
|
|
2574
|
+
padding: 0.1875rem 0;
|
|
2575
|
+
font-size: 0.75rem;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
.trace-view-name {
|
|
2579
|
+
width: 35%;
|
|
2580
|
+
flex-shrink: 0;
|
|
2581
|
+
display: flex;
|
|
2582
|
+
align-items: center;
|
|
2583
|
+
gap: 0.375rem;
|
|
2584
|
+
font-family: var(--font-mono);
|
|
2585
|
+
white-space: nowrap;
|
|
2586
|
+
overflow: hidden;
|
|
2587
|
+
text-overflow: ellipsis;
|
|
2588
|
+
color: var(--foreground);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
.trace-view-status-dot {
|
|
2592
|
+
width: 8px;
|
|
2593
|
+
height: 8px;
|
|
2594
|
+
border-radius: 50%;
|
|
2595
|
+
flex-shrink: 0;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
.trace-view-status-ok { background: var(--success); }
|
|
2599
|
+
.trace-view-status-error { background: var(--error); }
|
|
2600
|
+
.trace-view-status-unset { background: var(--muted-foreground); }
|
|
2601
|
+
|
|
2602
|
+
.trace-view-bar-container {
|
|
2603
|
+
flex: 1;
|
|
2604
|
+
position: relative;
|
|
2605
|
+
height: 1.25rem;
|
|
2606
|
+
background: var(--muted);
|
|
2607
|
+
border-radius: 2px;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
.trace-view-bar {
|
|
2611
|
+
position: absolute;
|
|
2612
|
+
top: 0;
|
|
2613
|
+
height: 100%;
|
|
2614
|
+
border-radius: 2px;
|
|
2615
|
+
min-width: 2px;
|
|
2616
|
+
display: flex;
|
|
2617
|
+
align-items: center;
|
|
2618
|
+
padding: 0 0.375rem;
|
|
2619
|
+
font-size: 0.625rem;
|
|
2620
|
+
font-family: var(--font-mono);
|
|
2621
|
+
color: white;
|
|
2622
|
+
white-space: nowrap;
|
|
2623
|
+
overflow: hidden;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
.trace-view-bar-ok { background: var(--success); }
|
|
2627
|
+
.trace-view-bar-error { background: var(--error); }
|
|
2628
|
+
.trace-view-bar-unset { background: var(--muted-foreground); }
|
|
2629
|
+
|
|
2630
|
+
@media print {
|
|
2631
|
+
.trace-view.collapsed .trace-view-content {
|
|
2632
|
+
display: block;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2486
2636
|
`;
|
|
2487
2637
|
|
|
2488
2638
|
// src/formatters/html/renderers/status.ts
|
|
@@ -2727,10 +2877,11 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
2727
2877
|
const isContinuation = CONTINUATION_KEYWORDS.includes(keywordTrimmed);
|
|
2728
2878
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
2729
2879
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
2880
|
+
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
2730
2881
|
return `<div class="${stepClass}">
|
|
2731
2882
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
2732
2883
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
2733
|
-
<span class="step-text">${
|
|
2884
|
+
<span class="step-text">${textHtml}</span>
|
|
2734
2885
|
<span class="step-duration">${duration}</span>
|
|
2735
2886
|
</div>${stepDocs}`;
|
|
2736
2887
|
}
|
|
@@ -2742,6 +2893,30 @@ function renderSteps(args, deps) {
|
|
|
2742
2893
|
return `<div class="steps">${stepsHtml}</div>`;
|
|
2743
2894
|
}
|
|
2744
2895
|
|
|
2896
|
+
// src/formatters/html/renderers/step-params.ts
|
|
2897
|
+
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
|
|
2898
|
+
function highlightStepParams(text, deps) {
|
|
2899
|
+
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
2900
|
+
if (matches.length === 0) {
|
|
2901
|
+
return deps.escapeHtml(text);
|
|
2902
|
+
}
|
|
2903
|
+
let result = "";
|
|
2904
|
+
let lastIndex = 0;
|
|
2905
|
+
for (const match of matches) {
|
|
2906
|
+
const matchStart = match.index;
|
|
2907
|
+
const matchEnd = matchStart + match[0].length;
|
|
2908
|
+
if (matchStart > lastIndex) {
|
|
2909
|
+
result += deps.escapeHtml(text.slice(lastIndex, matchStart));
|
|
2910
|
+
}
|
|
2911
|
+
result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
|
|
2912
|
+
lastIndex = matchEnd;
|
|
2913
|
+
}
|
|
2914
|
+
if (lastIndex < text.length) {
|
|
2915
|
+
result += deps.escapeHtml(text.slice(lastIndex));
|
|
2916
|
+
}
|
|
2917
|
+
return result;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2745
2920
|
// src/formatters/html/renderers/scenario.ts
|
|
2746
2921
|
function renderScenario(args, deps) {
|
|
2747
2922
|
const { tc } = args;
|
|
@@ -2749,6 +2924,19 @@ function renderScenario(args, deps) {
|
|
|
2749
2924
|
const statusClass = `status-${tc.status}`;
|
|
2750
2925
|
const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
|
|
2751
2926
|
const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
|
|
2927
|
+
const otelMeta = tc.story.meta?.otel;
|
|
2928
|
+
let traceBadge = "";
|
|
2929
|
+
if (otelMeta?.traceId) {
|
|
2930
|
+
const shortId = otelMeta.traceId.slice(0, 16);
|
|
2931
|
+
const traceLink = tc.story.docs?.find(
|
|
2932
|
+
(d) => d.kind === "link" && d.label === "View Trace"
|
|
2933
|
+
);
|
|
2934
|
+
if (traceLink) {
|
|
2935
|
+
traceBadge = `<a class="tag trace-tag" href="${deps.escapeHtml(traceLink.url)}" title="${deps.escapeHtml(otelMeta.traceId)}" target="_blank" rel="noopener">${deps.escapeHtml(shortId)}\u2026</a>`;
|
|
2936
|
+
} else {
|
|
2937
|
+
traceBadge = `<span class="tag trace-tag" title="${deps.escapeHtml(otelMeta.traceId)}">${deps.escapeHtml(shortId)}\u2026</span>`;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2752
2940
|
const storyDocs = deps.renderDocs(tc.story.docs, "story-docs");
|
|
2753
2941
|
const steps = deps.renderSteps(
|
|
2754
2942
|
{ steps: tc.story.steps, stepResults: tc.stepResults },
|
|
@@ -2769,6 +2957,10 @@ function renderScenario(args, deps) {
|
|
|
2769
2957
|
embedScreenshots: deps.embedScreenshots
|
|
2770
2958
|
}
|
|
2771
2959
|
);
|
|
2960
|
+
const traceView = deps.renderTraceView(
|
|
2961
|
+
{ spans: tc.story.otelSpans },
|
|
2962
|
+
{ escapeHtml: deps.escapeHtml }
|
|
2963
|
+
);
|
|
2772
2964
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
2773
2965
|
const ariaExpanded = !deps.startCollapsed;
|
|
2774
2966
|
return `
|
|
@@ -2779,7 +2971,7 @@ function renderScenario(args, deps) {
|
|
|
2779
2971
|
<span class="status-icon ${statusClass}">${statusIcon}</span>
|
|
2780
2972
|
<span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
|
|
2781
2973
|
</div>
|
|
2782
|
-
<div class="scenario-meta">${tags}</div>
|
|
2974
|
+
<div class="scenario-meta">${tags}${traceBadge}</div>
|
|
2783
2975
|
</div>
|
|
2784
2976
|
<span class="scenario-duration">${duration}</span>
|
|
2785
2977
|
</div>
|
|
@@ -2788,6 +2980,193 @@ function renderScenario(args, deps) {
|
|
|
2788
2980
|
${steps}
|
|
2789
2981
|
${error}
|
|
2790
2982
|
${attachments}
|
|
2983
|
+
${traceView}
|
|
2984
|
+
</div>
|
|
2985
|
+
</div>`;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// src/formatters/html/renderers/trace-view.ts
|
|
2989
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
|
|
2990
|
+
var TOOLTIP_MAX_LENGTH = 4096;
|
|
2991
|
+
function safeStatus(status) {
|
|
2992
|
+
return VALID_STATUSES.has(status) ? status : "unset";
|
|
2993
|
+
}
|
|
2994
|
+
function formatDuration(ms) {
|
|
2995
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
2996
|
+
return `${ms.toFixed(1)}ms`;
|
|
2997
|
+
}
|
|
2998
|
+
function clamp(value, min, max) {
|
|
2999
|
+
return Math.min(max, Math.max(min, value));
|
|
3000
|
+
}
|
|
3001
|
+
function normalizeSpans(spans) {
|
|
3002
|
+
const result = [];
|
|
3003
|
+
for (const span of spans) {
|
|
3004
|
+
if (!span || typeof span !== "object") continue;
|
|
3005
|
+
if (typeof span.spanId !== "string" || typeof span.name !== "string") continue;
|
|
3006
|
+
let startTimeMs;
|
|
3007
|
+
let durationMs;
|
|
3008
|
+
if (span.startTimeMs != null && span.durationMs != null) {
|
|
3009
|
+
startTimeMs = span.startTimeMs;
|
|
3010
|
+
durationMs = span.durationMs;
|
|
3011
|
+
} else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
|
|
3012
|
+
startTimeMs = span.startTimeUnixNano / 1e6;
|
|
3013
|
+
durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
|
|
3014
|
+
} else {
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
3017
|
+
durationMs = Math.max(0, durationMs);
|
|
3018
|
+
if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
|
|
3019
|
+
result.push({
|
|
3020
|
+
spanId: span.spanId,
|
|
3021
|
+
parentSpanId: span.parentSpanId,
|
|
3022
|
+
name: span.name,
|
|
3023
|
+
startTimeMs,
|
|
3024
|
+
durationMs,
|
|
3025
|
+
status: safeStatus(span.status),
|
|
3026
|
+
statusMessage: span.statusMessage,
|
|
3027
|
+
attributes: span.attributes
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
return result;
|
|
3031
|
+
}
|
|
3032
|
+
function buildTree(spans) {
|
|
3033
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3034
|
+
for (const span of spans) {
|
|
3035
|
+
let key = span.spanId;
|
|
3036
|
+
if (byId.has(key)) {
|
|
3037
|
+
let suffix = 2;
|
|
3038
|
+
while (byId.has(`${span.spanId}__dup${suffix}`)) suffix++;
|
|
3039
|
+
key = `${span.spanId}__dup${suffix}`;
|
|
3040
|
+
}
|
|
3041
|
+
byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
|
|
3042
|
+
}
|
|
3043
|
+
const roots = [];
|
|
3044
|
+
for (const node of byId.values()) {
|
|
3045
|
+
const parentId = node.span.parentSpanId;
|
|
3046
|
+
const parent = parentId ? byId.get(parentId) : void 0;
|
|
3047
|
+
if (parent && parent !== node) {
|
|
3048
|
+
parent.children.push(node);
|
|
3049
|
+
} else {
|
|
3050
|
+
roots.push(node);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
for (const node of byId.values()) {
|
|
3054
|
+
node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3055
|
+
}
|
|
3056
|
+
roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3057
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3058
|
+
function assignDepth(node, depth) {
|
|
3059
|
+
if (visited.has(node.span.spanId)) return;
|
|
3060
|
+
visited.add(node.span.spanId);
|
|
3061
|
+
node.depth = depth;
|
|
3062
|
+
for (const child of node.children) {
|
|
3063
|
+
assignDepth(child, depth + 1);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
for (const root of roots) {
|
|
3067
|
+
assignDepth(root, 0);
|
|
3068
|
+
}
|
|
3069
|
+
for (const node of byId.values()) {
|
|
3070
|
+
if (!visited.has(node.span.spanId)) {
|
|
3071
|
+
node.children = [];
|
|
3072
|
+
roots.push(node);
|
|
3073
|
+
assignDepth(node, 0);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3077
|
+
return roots;
|
|
3078
|
+
}
|
|
3079
|
+
function flattenTree(roots) {
|
|
3080
|
+
const result = [];
|
|
3081
|
+
function walk(node) {
|
|
3082
|
+
result.push(node);
|
|
3083
|
+
for (const child of node.children) {
|
|
3084
|
+
walk(child);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
for (const root of roots) {
|
|
3088
|
+
walk(root);
|
|
3089
|
+
}
|
|
3090
|
+
return result;
|
|
3091
|
+
}
|
|
3092
|
+
function buildTooltip(span, escapeHtml2) {
|
|
3093
|
+
const parts = [];
|
|
3094
|
+
parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
|
|
3095
|
+
if (span.statusMessage) {
|
|
3096
|
+
parts.push(`Status: ${span.statusMessage}`);
|
|
3097
|
+
}
|
|
3098
|
+
if (span.attributes) {
|
|
3099
|
+
const keys = Object.keys(span.attributes).sort();
|
|
3100
|
+
for (const key of keys) {
|
|
3101
|
+
const val = span.attributes[key];
|
|
3102
|
+
const formatted = Array.isArray(val) ? `[${val.map((v) => String(v)).join(", ")}]` : String(val);
|
|
3103
|
+
parts.push(`${key}=${formatted}`);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
let text = parts.join("\n");
|
|
3107
|
+
if (text.length > TOOLTIP_MAX_LENGTH) {
|
|
3108
|
+
text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
|
|
3109
|
+
}
|
|
3110
|
+
return escapeHtml2(text);
|
|
3111
|
+
}
|
|
3112
|
+
function renderTraceView(args, deps) {
|
|
3113
|
+
if (!args.spans || args.spans.length === 0) return "";
|
|
3114
|
+
const normalized = normalizeSpans(args.spans);
|
|
3115
|
+
if (normalized.length === 0) return "";
|
|
3116
|
+
const roots = buildTree(normalized);
|
|
3117
|
+
const flat = flattenTree(roots);
|
|
3118
|
+
let minStart = Infinity;
|
|
3119
|
+
let maxEnd = -Infinity;
|
|
3120
|
+
for (const node of flat) {
|
|
3121
|
+
const s = node.span.startTimeMs;
|
|
3122
|
+
const e = s + node.span.durationMs;
|
|
3123
|
+
if (s < minStart) minStart = s;
|
|
3124
|
+
if (e > maxEnd) maxEnd = e;
|
|
3125
|
+
}
|
|
3126
|
+
let totalDuration = maxEnd - minStart;
|
|
3127
|
+
if (totalDuration <= 0) totalDuration = 1;
|
|
3128
|
+
const rows = flat.map((node) => {
|
|
3129
|
+
const { span, depth } = node;
|
|
3130
|
+
const indent = depth * 16;
|
|
3131
|
+
const minWidth = 0.5;
|
|
3132
|
+
let spanLeft = clamp(
|
|
3133
|
+
(span.startTimeMs - minStart) / totalDuration * 100,
|
|
3134
|
+
0,
|
|
3135
|
+
100
|
|
3136
|
+
);
|
|
3137
|
+
if (spanLeft + minWidth > 100) {
|
|
3138
|
+
spanLeft = 100 - minWidth;
|
|
3139
|
+
}
|
|
3140
|
+
const spanWidth = clamp(
|
|
3141
|
+
span.durationMs / totalDuration * 100,
|
|
3142
|
+
minWidth,
|
|
3143
|
+
100 - spanLeft
|
|
3144
|
+
);
|
|
3145
|
+
const tooltip = buildTooltip(span, deps.escapeHtml);
|
|
3146
|
+
const durationLabel = formatDuration(span.durationMs);
|
|
3147
|
+
return ` <div class="trace-view-row">
|
|
3148
|
+
<div class="trace-view-name" style="padding-left: ${indent}px" title="${deps.escapeHtml(span.name)}">
|
|
3149
|
+
<span class="trace-view-status-dot trace-view-status-${span.status}"></span>
|
|
3150
|
+
${deps.escapeHtml(span.name)}
|
|
3151
|
+
</div>
|
|
3152
|
+
<div class="trace-view-bar-container">
|
|
3153
|
+
<div class="trace-view-bar trace-view-bar-${span.status}" style="left: ${spanLeft.toFixed(2)}%; width: ${spanWidth.toFixed(2)}%" title="${tooltip}">${durationLabel}</div>
|
|
3154
|
+
</div>
|
|
3155
|
+
</div>`;
|
|
3156
|
+
}).join("\n");
|
|
3157
|
+
const axisEnd = formatDuration(maxEnd - minStart);
|
|
3158
|
+
return `<div class="trace-view collapsed">
|
|
3159
|
+
<div class="trace-view-header" role="button" tabindex="0" aria-expanded="false">
|
|
3160
|
+
<span>Spans</span>
|
|
3161
|
+
<span class="trace-view-count">${flat.length}</span>
|
|
3162
|
+
<span class="chevron">▼</span>
|
|
3163
|
+
</div>
|
|
3164
|
+
<div class="trace-view-content">
|
|
3165
|
+
<div class="trace-view-axis">
|
|
3166
|
+
<span>0ms</span>
|
|
3167
|
+
<span>${axisEnd}</span>
|
|
3168
|
+
</div>
|
|
3169
|
+
${rows}
|
|
2791
3170
|
</div>
|
|
2792
3171
|
</div>`;
|
|
2793
3172
|
}
|
|
@@ -2913,7 +3292,8 @@ function createHtmlFormatter(options = {}) {
|
|
|
2913
3292
|
const stepsDeps = {
|
|
2914
3293
|
escapeHtml,
|
|
2915
3294
|
getStatusIcon,
|
|
2916
|
-
renderDocs
|
|
3295
|
+
renderDocs,
|
|
3296
|
+
highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
|
|
2917
3297
|
};
|
|
2918
3298
|
const scenarioDeps = {
|
|
2919
3299
|
escapeHtml,
|
|
@@ -2923,6 +3303,7 @@ function createHtmlFormatter(options = {}) {
|
|
|
2923
3303
|
renderDocs,
|
|
2924
3304
|
renderErrorBox: (args, d) => renderErrorBox(args, d),
|
|
2925
3305
|
renderAttachments: (args, d) => renderAttachments(args, d),
|
|
3306
|
+
renderTraceView: (args, d) => renderTraceView(args, d),
|
|
2926
3307
|
embedScreenshots: opts.embedScreenshots
|
|
2927
3308
|
};
|
|
2928
3309
|
const featureDeps = {
|
|
@@ -3215,6 +3596,7 @@ var MarkdownFormatter = class {
|
|
|
3215
3596
|
includeSummaryTable: options.includeSummaryTable ?? false,
|
|
3216
3597
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
3217
3598
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
3599
|
+
traceUrlTemplate: options.traceUrlTemplate,
|
|
3218
3600
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
3219
3601
|
customRenderers: options.customRenderers
|
|
3220
3602
|
};
|
|
@@ -3437,6 +3819,18 @@ var MarkdownFormatter = class {
|
|
|
3437
3819
|
meta.push(`Tickets: ${tc.story.tickets.map((t) => `\`${t}\``).join(", ")}`);
|
|
3438
3820
|
}
|
|
3439
3821
|
}
|
|
3822
|
+
const otelMeta = tc.story.meta?.otel;
|
|
3823
|
+
if (otelMeta?.traceId) {
|
|
3824
|
+
const traceTemplate = this.options.traceUrlTemplate;
|
|
3825
|
+
if (traceTemplate) {
|
|
3826
|
+
const url = traceTemplate.replace(/\{traceId\}/g, otelMeta.traceId);
|
|
3827
|
+
meta.push(
|
|
3828
|
+
`Trace: [${otelMeta.traceId.slice(0, 16)}\u2026](${url})`
|
|
3829
|
+
);
|
|
3830
|
+
} else {
|
|
3831
|
+
meta.push(`Trace: \`${otelMeta.traceId}\``);
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3440
3834
|
if (meta.length > 0) {
|
|
3441
3835
|
lines.push(meta.join(" | "));
|
|
3442
3836
|
}
|
|
@@ -5275,7 +5669,7 @@ function readBranchName(cwd = process.cwd()) {
|
|
|
5275
5669
|
}
|
|
5276
5670
|
|
|
5277
5671
|
// src/utils/duration.ts
|
|
5278
|
-
function
|
|
5672
|
+
function formatDuration2(ms) {
|
|
5279
5673
|
if (ms < 1e3) {
|
|
5280
5674
|
return `${Math.round(ms)} ms`;
|
|
5281
5675
|
}
|
|
@@ -5379,6 +5773,32 @@ function detectCI4(env = process.env) {
|
|
|
5379
5773
|
return void 0;
|
|
5380
5774
|
}
|
|
5381
5775
|
|
|
5776
|
+
// src/utils/otel-detect.ts
|
|
5777
|
+
var import_node_module = require("module");
|
|
5778
|
+
var import_meta2 = {};
|
|
5779
|
+
function getRequire() {
|
|
5780
|
+
const url = import_meta2.url ?? (typeof __filename !== "undefined" ? `file://${__filename}` : void 0);
|
|
5781
|
+
if (!url) throw new Error("Cannot determine module URL");
|
|
5782
|
+
return (0, import_node_module.createRequire)(url);
|
|
5783
|
+
}
|
|
5784
|
+
function tryGetActiveOtelContext() {
|
|
5785
|
+
try {
|
|
5786
|
+
const api = getRequire()("@opentelemetry/api");
|
|
5787
|
+
const span = api.trace?.getActiveSpan?.();
|
|
5788
|
+
if (!span) return void 0;
|
|
5789
|
+
const ctx = span.spanContext?.();
|
|
5790
|
+
if (!ctx?.traceId || ctx.traceId === "00000000000000000000000000000000")
|
|
5791
|
+
return void 0;
|
|
5792
|
+
return { traceId: ctx.traceId, spanId: ctx.spanId };
|
|
5793
|
+
} catch {
|
|
5794
|
+
return void 0;
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
function resolveTraceUrl(template, traceId) {
|
|
5798
|
+
if (!template) return void 0;
|
|
5799
|
+
return template.replace(/\{traceId\}/g, traceId);
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5382
5802
|
// src/index.ts
|
|
5383
5803
|
var FORMAT_EXTENSIONS = {
|
|
5384
5804
|
markdown: ".md",
|
|
@@ -5569,6 +5989,7 @@ var ReportGenerator = class {
|
|
|
5569
5989
|
includeSummaryTable: options.markdown?.includeSummaryTable ?? false,
|
|
5570
5990
|
permalinkBaseUrl: options.markdown?.permalinkBaseUrl,
|
|
5571
5991
|
ticketUrlTemplate: options.markdown?.ticketUrlTemplate,
|
|
5992
|
+
traceUrlTemplate: options.markdown?.traceUrlTemplate,
|
|
5572
5993
|
includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
|
|
5573
5994
|
customRenderers: options.markdown?.customRenderers
|
|
5574
5995
|
}
|
|
@@ -5694,6 +6115,7 @@ var ReportGenerator = class {
|
|
|
5694
6115
|
includeSummaryTable: this.options.markdown.includeSummaryTable,
|
|
5695
6116
|
permalinkBaseUrl: this.options.markdown.permalinkBaseUrl,
|
|
5696
6117
|
ticketUrlTemplate: this.options.markdown.ticketUrlTemplate,
|
|
6118
|
+
traceUrlTemplate: this.options.markdown.traceUrlTemplate,
|
|
5697
6119
|
includeSourceLinks: this.options.markdown.includeSourceLinks,
|
|
5698
6120
|
customRenderers: this.options.markdown.customRenderers
|
|
5699
6121
|
});
|
|
@@ -5756,7 +6178,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
5756
6178
|
readPackageVersion,
|
|
5757
6179
|
resolveAttachment,
|
|
5758
6180
|
resolveAttachments,
|
|
6181
|
+
resolveTraceUrl,
|
|
5759
6182
|
slugify,
|
|
6183
|
+
tryGetActiveOtelContext,
|
|
5760
6184
|
validateCanonicalRun
|
|
5761
6185
|
});
|
|
5762
6186
|
//# sourceMappingURL=index.cjs.map
|