executable-stories-formatters 0.3.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.
@@ -1 +1 @@
1
- export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, l as PlaywrightAnnotation, m as PlaywrightAttachment, n as PlaywrightError, o as PlaywrightLocation, p as PlaywrightStatus, q as PlaywrightTestCase, r as PlaywrightTestResult, x as StoryFileReport, V as VitestAdapterOptions, y as VitestSerializedError, z as VitestState, A as VitestTestCase, B as VitestTestModule, C as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DCJ0NvAp.cjs';
1
+ export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.cjs';
@@ -1 +1 @@
1
- export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, l as PlaywrightAnnotation, m as PlaywrightAttachment, n as PlaywrightError, o as PlaywrightLocation, p as PlaywrightStatus, q as PlaywrightTestCase, r as PlaywrightTestResult, x as StoryFileReport, V as VitestAdapterOptions, y as VitestSerializedError, z as VitestState, A as VitestTestCase, B as VitestTestModule, C as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DCJ0NvAp.js';
1
+ export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.js';
package/dist/cli.js CHANGED
@@ -1101,17 +1101,17 @@ function initCollapse() {
1101
1101
  }
1102
1102
 
1103
1103
  function expandAll() {
1104
- document.querySelectorAll('.feature, .scenario').forEach(el => {
1104
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
1105
1105
  el.classList.remove('collapsed');
1106
- const header = el.querySelector('.feature-header, .scenario-header');
1106
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1107
1107
  header?.setAttribute('aria-expanded', 'true');
1108
1108
  });
1109
1109
  }
1110
1110
 
1111
1111
  function collapseAll() {
1112
- document.querySelectorAll('.feature, .scenario').forEach(el => {
1112
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
1113
1113
  el.classList.add('collapsed');
1114
- const header = el.querySelector('.feature-header, .scenario-header');
1114
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1115
1115
  header?.setAttribute('aria-expanded', 'false');
1116
1116
  });
1117
1117
  }
@@ -2638,6 +2638,132 @@ body {
2638
2638
  background: none;
2639
2639
  }
2640
2640
 
2641
+ /* ============================================================================
2642
+ Trace View - OTel span waterfall
2643
+ ============================================================================ */
2644
+ .trace-view {
2645
+ margin-top: 0.75rem;
2646
+ border: 1px solid var(--border);
2647
+ border-radius: calc(var(--radius) - 2px);
2648
+ overflow: hidden;
2649
+ }
2650
+
2651
+ .trace-view-header {
2652
+ display: flex;
2653
+ align-items: center;
2654
+ gap: 0.5rem;
2655
+ padding: 0.5rem 0.75rem;
2656
+ background: var(--card);
2657
+ cursor: pointer;
2658
+ user-select: none;
2659
+ font-size: 0.8125rem;
2660
+ font-weight: 500;
2661
+ color: var(--foreground);
2662
+ transition: background-color 0.15s ease;
2663
+ }
2664
+
2665
+ .trace-view-header:hover {
2666
+ background: var(--accordion-header-hover);
2667
+ }
2668
+
2669
+ .trace-view-count {
2670
+ font-size: 0.6875rem;
2671
+ font-weight: 500;
2672
+ padding: 0.125rem 0.5rem;
2673
+ background: var(--success-light);
2674
+ color: var(--success);
2675
+ border: 1px solid var(--success-border);
2676
+ border-radius: 9999px;
2677
+ font-family: var(--font-mono);
2678
+ }
2679
+
2680
+ .trace-view-content {
2681
+ border-top: 1px solid var(--border);
2682
+ padding: 0.5rem 0.75rem;
2683
+ background: var(--accordion-content-bg);
2684
+ }
2685
+
2686
+ .trace-view.collapsed .trace-view-content {
2687
+ display: none;
2688
+ }
2689
+
2690
+ .trace-view-axis {
2691
+ display: flex;
2692
+ justify-content: space-between;
2693
+ font-size: 0.625rem;
2694
+ font-family: var(--font-mono);
2695
+ color: var(--muted-foreground);
2696
+ padding-bottom: 0.375rem;
2697
+ margin-bottom: 0.375rem;
2698
+ border-bottom: 1px solid var(--border);
2699
+ }
2700
+
2701
+ .trace-view-row {
2702
+ display: flex;
2703
+ align-items: center;
2704
+ gap: 0.5rem;
2705
+ padding: 0.1875rem 0;
2706
+ font-size: 0.75rem;
2707
+ }
2708
+
2709
+ .trace-view-name {
2710
+ width: 35%;
2711
+ flex-shrink: 0;
2712
+ display: flex;
2713
+ align-items: center;
2714
+ gap: 0.375rem;
2715
+ font-family: var(--font-mono);
2716
+ white-space: nowrap;
2717
+ overflow: hidden;
2718
+ text-overflow: ellipsis;
2719
+ color: var(--foreground);
2720
+ }
2721
+
2722
+ .trace-view-status-dot {
2723
+ width: 8px;
2724
+ height: 8px;
2725
+ border-radius: 50%;
2726
+ flex-shrink: 0;
2727
+ }
2728
+
2729
+ .trace-view-status-ok { background: var(--success); }
2730
+ .trace-view-status-error { background: var(--error); }
2731
+ .trace-view-status-unset { background: var(--muted-foreground); }
2732
+
2733
+ .trace-view-bar-container {
2734
+ flex: 1;
2735
+ position: relative;
2736
+ height: 1.25rem;
2737
+ background: var(--muted);
2738
+ border-radius: 2px;
2739
+ }
2740
+
2741
+ .trace-view-bar {
2742
+ position: absolute;
2743
+ top: 0;
2744
+ height: 100%;
2745
+ border-radius: 2px;
2746
+ min-width: 2px;
2747
+ display: flex;
2748
+ align-items: center;
2749
+ padding: 0 0.375rem;
2750
+ font-size: 0.625rem;
2751
+ font-family: var(--font-mono);
2752
+ color: white;
2753
+ white-space: nowrap;
2754
+ overflow: hidden;
2755
+ }
2756
+
2757
+ .trace-view-bar-ok { background: var(--success); }
2758
+ .trace-view-bar-error { background: var(--error); }
2759
+ .trace-view-bar-unset { background: var(--muted-foreground); }
2760
+
2761
+ @media print {
2762
+ .trace-view.collapsed .trace-view-content {
2763
+ display: block;
2764
+ }
2765
+ }
2766
+
2641
2767
  `;
2642
2768
 
2643
2769
  // src/formatters/html/renderers/status.ts
@@ -2962,6 +3088,10 @@ function renderScenario(args, deps) {
2962
3088
  embedScreenshots: deps.embedScreenshots
2963
3089
  }
2964
3090
  );
3091
+ const traceView = deps.renderTraceView(
3092
+ { spans: tc.story.otelSpans },
3093
+ { escapeHtml: deps.escapeHtml }
3094
+ );
2965
3095
  const collapsedClass = deps.startCollapsed ? " collapsed" : "";
2966
3096
  const ariaExpanded = !deps.startCollapsed;
2967
3097
  return `
@@ -2981,6 +3111,193 @@ function renderScenario(args, deps) {
2981
3111
  ${steps}
2982
3112
  ${error}
2983
3113
  ${attachments}
3114
+ ${traceView}
3115
+ </div>
3116
+ </div>`;
3117
+ }
3118
+
3119
+ // src/formatters/html/renderers/trace-view.ts
3120
+ var VALID_STATUSES = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
3121
+ var TOOLTIP_MAX_LENGTH = 4096;
3122
+ function safeStatus(status) {
3123
+ return VALID_STATUSES.has(status) ? status : "unset";
3124
+ }
3125
+ function formatDuration(ms) {
3126
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
3127
+ return `${ms.toFixed(1)}ms`;
3128
+ }
3129
+ function clamp(value, min, max) {
3130
+ return Math.min(max, Math.max(min, value));
3131
+ }
3132
+ function normalizeSpans(spans) {
3133
+ const result = [];
3134
+ for (const span of spans) {
3135
+ if (!span || typeof span !== "object") continue;
3136
+ if (typeof span.spanId !== "string" || typeof span.name !== "string") continue;
3137
+ let startTimeMs;
3138
+ let durationMs;
3139
+ if (span.startTimeMs != null && span.durationMs != null) {
3140
+ startTimeMs = span.startTimeMs;
3141
+ durationMs = span.durationMs;
3142
+ } else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
3143
+ startTimeMs = span.startTimeUnixNano / 1e6;
3144
+ durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
3145
+ } else {
3146
+ continue;
3147
+ }
3148
+ durationMs = Math.max(0, durationMs);
3149
+ if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
3150
+ result.push({
3151
+ spanId: span.spanId,
3152
+ parentSpanId: span.parentSpanId,
3153
+ name: span.name,
3154
+ startTimeMs,
3155
+ durationMs,
3156
+ status: safeStatus(span.status),
3157
+ statusMessage: span.statusMessage,
3158
+ attributes: span.attributes
3159
+ });
3160
+ }
3161
+ return result;
3162
+ }
3163
+ function buildTree(spans) {
3164
+ const byId = /* @__PURE__ */ new Map();
3165
+ for (const span of spans) {
3166
+ let key = span.spanId;
3167
+ if (byId.has(key)) {
3168
+ let suffix = 2;
3169
+ while (byId.has(`${span.spanId}__dup${suffix}`)) suffix++;
3170
+ key = `${span.spanId}__dup${suffix}`;
3171
+ }
3172
+ byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
3173
+ }
3174
+ const roots = [];
3175
+ for (const node of byId.values()) {
3176
+ const parentId = node.span.parentSpanId;
3177
+ const parent = parentId ? byId.get(parentId) : void 0;
3178
+ if (parent && parent !== node) {
3179
+ parent.children.push(node);
3180
+ } else {
3181
+ roots.push(node);
3182
+ }
3183
+ }
3184
+ for (const node of byId.values()) {
3185
+ node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
3186
+ }
3187
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
3188
+ const visited = /* @__PURE__ */ new Set();
3189
+ function assignDepth(node, depth) {
3190
+ if (visited.has(node.span.spanId)) return;
3191
+ visited.add(node.span.spanId);
3192
+ node.depth = depth;
3193
+ for (const child of node.children) {
3194
+ assignDepth(child, depth + 1);
3195
+ }
3196
+ }
3197
+ for (const root of roots) {
3198
+ assignDepth(root, 0);
3199
+ }
3200
+ for (const node of byId.values()) {
3201
+ if (!visited.has(node.span.spanId)) {
3202
+ node.children = [];
3203
+ roots.push(node);
3204
+ assignDepth(node, 0);
3205
+ }
3206
+ }
3207
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
3208
+ return roots;
3209
+ }
3210
+ function flattenTree(roots) {
3211
+ const result = [];
3212
+ function walk(node) {
3213
+ result.push(node);
3214
+ for (const child of node.children) {
3215
+ walk(child);
3216
+ }
3217
+ }
3218
+ for (const root of roots) {
3219
+ walk(root);
3220
+ }
3221
+ return result;
3222
+ }
3223
+ function buildTooltip(span, escapeHtml2) {
3224
+ const parts = [];
3225
+ parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
3226
+ if (span.statusMessage) {
3227
+ parts.push(`Status: ${span.statusMessage}`);
3228
+ }
3229
+ if (span.attributes) {
3230
+ const keys = Object.keys(span.attributes).sort();
3231
+ for (const key of keys) {
3232
+ const val = span.attributes[key];
3233
+ const formatted = Array.isArray(val) ? `[${val.map((v) => String(v)).join(", ")}]` : String(val);
3234
+ parts.push(`${key}=${formatted}`);
3235
+ }
3236
+ }
3237
+ let text = parts.join("\n");
3238
+ if (text.length > TOOLTIP_MAX_LENGTH) {
3239
+ text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
3240
+ }
3241
+ return escapeHtml2(text);
3242
+ }
3243
+ function renderTraceView(args, deps) {
3244
+ if (!args.spans || args.spans.length === 0) return "";
3245
+ const normalized = normalizeSpans(args.spans);
3246
+ if (normalized.length === 0) return "";
3247
+ const roots = buildTree(normalized);
3248
+ const flat = flattenTree(roots);
3249
+ let minStart = Infinity;
3250
+ let maxEnd = -Infinity;
3251
+ for (const node of flat) {
3252
+ const s = node.span.startTimeMs;
3253
+ const e = s + node.span.durationMs;
3254
+ if (s < minStart) minStart = s;
3255
+ if (e > maxEnd) maxEnd = e;
3256
+ }
3257
+ let totalDuration = maxEnd - minStart;
3258
+ if (totalDuration <= 0) totalDuration = 1;
3259
+ const rows = flat.map((node) => {
3260
+ const { span, depth } = node;
3261
+ const indent = depth * 16;
3262
+ const minWidth = 0.5;
3263
+ let spanLeft = clamp(
3264
+ (span.startTimeMs - minStart) / totalDuration * 100,
3265
+ 0,
3266
+ 100
3267
+ );
3268
+ if (spanLeft + minWidth > 100) {
3269
+ spanLeft = 100 - minWidth;
3270
+ }
3271
+ const spanWidth = clamp(
3272
+ span.durationMs / totalDuration * 100,
3273
+ minWidth,
3274
+ 100 - spanLeft
3275
+ );
3276
+ const tooltip = buildTooltip(span, deps.escapeHtml);
3277
+ const durationLabel = formatDuration(span.durationMs);
3278
+ return ` <div class="trace-view-row">
3279
+ <div class="trace-view-name" style="padding-left: ${indent}px" title="${deps.escapeHtml(span.name)}">
3280
+ <span class="trace-view-status-dot trace-view-status-${span.status}"></span>
3281
+ ${deps.escapeHtml(span.name)}
3282
+ </div>
3283
+ <div class="trace-view-bar-container">
3284
+ <div class="trace-view-bar trace-view-bar-${span.status}" style="left: ${spanLeft.toFixed(2)}%; width: ${spanWidth.toFixed(2)}%" title="${tooltip}">${durationLabel}</div>
3285
+ </div>
3286
+ </div>`;
3287
+ }).join("\n");
3288
+ const axisEnd = formatDuration(maxEnd - minStart);
3289
+ return `<div class="trace-view collapsed">
3290
+ <div class="trace-view-header" role="button" tabindex="0" aria-expanded="false">
3291
+ <span>Spans</span>
3292
+ <span class="trace-view-count">${flat.length}</span>
3293
+ <span class="chevron">&#9660;</span>
3294
+ </div>
3295
+ <div class="trace-view-content">
3296
+ <div class="trace-view-axis">
3297
+ <span>0ms</span>
3298
+ <span>${axisEnd}</span>
3299
+ </div>
3300
+ ${rows}
2984
3301
  </div>
2985
3302
  </div>`;
2986
3303
  }
@@ -3117,6 +3434,7 @@ function createHtmlFormatter(options = {}) {
3117
3434
  renderDocs,
3118
3435
  renderErrorBox: (args, d) => renderErrorBox(args, d),
3119
3436
  renderAttachments: (args, d) => renderAttachments(args, d),
3437
+ renderTraceView: (args, d) => renderTraceView(args, d),
3120
3438
  embedScreenshots: opts.embedScreenshots
3121
3439
  };
3122
3440
  const featureDeps = {