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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StoryMeta, a as StoryStep, D as DocEntry, R as RawStatus, b as RawAttachment, c as RawRun, d as RawCIInfo, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DCJ0NvAp.cjs';
2
- export { h as DocPhase, 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, s as RawStepEvent, t as RawTestCase, u as STORY_META_KEY, v as StepKeyword, w as StepMode, x as StoryFileReport, V as VitestAdapterOptions, y as VitestSerializedError, z as VitestState, A as VitestTestCase, B as VitestTestModule, C as VitestTestResult } from './index-DCJ0NvAp.cjs';
1
+ import { S as StoryMeta, a as StoryStep, D as DocEntry, R as RawStatus, b as RawAttachment, c as RawRun, d as RawCIInfo, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.cjs';
2
+ export { h as DocPhase, J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, O as OtelAttributeValue, l as OtelSpan, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, t as RawStepEvent, u as RawTestCase, v as STORY_META_KEY, w as StepKeyword, x as StepMode, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult } from './index-DyeUWfYK.cjs';
3
3
 
4
4
  /**
5
5
  * Canonical types for Layer 2: Anti-Corruption Layer output.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StoryMeta, a as StoryStep, D as DocEntry, R as RawStatus, b as RawAttachment, c as RawRun, d as RawCIInfo, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DCJ0NvAp.js';
2
- export { h as DocPhase, 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, s as RawStepEvent, t as RawTestCase, u as STORY_META_KEY, v as StepKeyword, w as StepMode, x as StoryFileReport, V as VitestAdapterOptions, y as VitestSerializedError, z as VitestState, A as VitestTestCase, B as VitestTestModule, C as VitestTestResult } from './index-DCJ0NvAp.js';
1
+ import { S as StoryMeta, a as StoryStep, D as DocEntry, R as RawStatus, b as RawAttachment, c as RawRun, d as RawCIInfo, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.js';
2
+ export { h as DocPhase, J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, O as OtelAttributeValue, l as OtelSpan, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, t as RawStepEvent, u as RawTestCase, v as STORY_META_KEY, w as StepKeyword, x as StepMode, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult } from './index-DyeUWfYK.js';
3
3
 
4
4
  /**
5
5
  * Canonical types for Layer 2: Anti-Corruption Layer output.
package/dist/index.js CHANGED
@@ -898,17 +898,17 @@ function initCollapse() {
898
898
  }
899
899
 
900
900
  function expandAll() {
901
- document.querySelectorAll('.feature, .scenario').forEach(el => {
901
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
902
902
  el.classList.remove('collapsed');
903
- const header = el.querySelector('.feature-header, .scenario-header');
903
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
904
904
  header?.setAttribute('aria-expanded', 'true');
905
905
  });
906
906
  }
907
907
 
908
908
  function collapseAll() {
909
- document.querySelectorAll('.feature, .scenario').forEach(el => {
909
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
910
910
  el.classList.add('collapsed');
911
- const header = el.querySelector('.feature-header, .scenario-header');
911
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
912
912
  header?.setAttribute('aria-expanded', 'false');
913
913
  });
914
914
  }
@@ -2435,6 +2435,132 @@ body {
2435
2435
  background: none;
2436
2436
  }
2437
2437
 
2438
+ /* ============================================================================
2439
+ Trace View - OTel span waterfall
2440
+ ============================================================================ */
2441
+ .trace-view {
2442
+ margin-top: 0.75rem;
2443
+ border: 1px solid var(--border);
2444
+ border-radius: calc(var(--radius) - 2px);
2445
+ overflow: hidden;
2446
+ }
2447
+
2448
+ .trace-view-header {
2449
+ display: flex;
2450
+ align-items: center;
2451
+ gap: 0.5rem;
2452
+ padding: 0.5rem 0.75rem;
2453
+ background: var(--card);
2454
+ cursor: pointer;
2455
+ user-select: none;
2456
+ font-size: 0.8125rem;
2457
+ font-weight: 500;
2458
+ color: var(--foreground);
2459
+ transition: background-color 0.15s ease;
2460
+ }
2461
+
2462
+ .trace-view-header:hover {
2463
+ background: var(--accordion-header-hover);
2464
+ }
2465
+
2466
+ .trace-view-count {
2467
+ font-size: 0.6875rem;
2468
+ font-weight: 500;
2469
+ padding: 0.125rem 0.5rem;
2470
+ background: var(--success-light);
2471
+ color: var(--success);
2472
+ border: 1px solid var(--success-border);
2473
+ border-radius: 9999px;
2474
+ font-family: var(--font-mono);
2475
+ }
2476
+
2477
+ .trace-view-content {
2478
+ border-top: 1px solid var(--border);
2479
+ padding: 0.5rem 0.75rem;
2480
+ background: var(--accordion-content-bg);
2481
+ }
2482
+
2483
+ .trace-view.collapsed .trace-view-content {
2484
+ display: none;
2485
+ }
2486
+
2487
+ .trace-view-axis {
2488
+ display: flex;
2489
+ justify-content: space-between;
2490
+ font-size: 0.625rem;
2491
+ font-family: var(--font-mono);
2492
+ color: var(--muted-foreground);
2493
+ padding-bottom: 0.375rem;
2494
+ margin-bottom: 0.375rem;
2495
+ border-bottom: 1px solid var(--border);
2496
+ }
2497
+
2498
+ .trace-view-row {
2499
+ display: flex;
2500
+ align-items: center;
2501
+ gap: 0.5rem;
2502
+ padding: 0.1875rem 0;
2503
+ font-size: 0.75rem;
2504
+ }
2505
+
2506
+ .trace-view-name {
2507
+ width: 35%;
2508
+ flex-shrink: 0;
2509
+ display: flex;
2510
+ align-items: center;
2511
+ gap: 0.375rem;
2512
+ font-family: var(--font-mono);
2513
+ white-space: nowrap;
2514
+ overflow: hidden;
2515
+ text-overflow: ellipsis;
2516
+ color: var(--foreground);
2517
+ }
2518
+
2519
+ .trace-view-status-dot {
2520
+ width: 8px;
2521
+ height: 8px;
2522
+ border-radius: 50%;
2523
+ flex-shrink: 0;
2524
+ }
2525
+
2526
+ .trace-view-status-ok { background: var(--success); }
2527
+ .trace-view-status-error { background: var(--error); }
2528
+ .trace-view-status-unset { background: var(--muted-foreground); }
2529
+
2530
+ .trace-view-bar-container {
2531
+ flex: 1;
2532
+ position: relative;
2533
+ height: 1.25rem;
2534
+ background: var(--muted);
2535
+ border-radius: 2px;
2536
+ }
2537
+
2538
+ .trace-view-bar {
2539
+ position: absolute;
2540
+ top: 0;
2541
+ height: 100%;
2542
+ border-radius: 2px;
2543
+ min-width: 2px;
2544
+ display: flex;
2545
+ align-items: center;
2546
+ padding: 0 0.375rem;
2547
+ font-size: 0.625rem;
2548
+ font-family: var(--font-mono);
2549
+ color: white;
2550
+ white-space: nowrap;
2551
+ overflow: hidden;
2552
+ }
2553
+
2554
+ .trace-view-bar-ok { background: var(--success); }
2555
+ .trace-view-bar-error { background: var(--error); }
2556
+ .trace-view-bar-unset { background: var(--muted-foreground); }
2557
+
2558
+ @media print {
2559
+ .trace-view.collapsed .trace-view-content {
2560
+ display: block;
2561
+ }
2562
+ }
2563
+
2438
2564
  `;
2439
2565
 
2440
2566
  // src/formatters/html/renderers/status.ts
@@ -2759,6 +2885,10 @@ function renderScenario(args, deps) {
2759
2885
  embedScreenshots: deps.embedScreenshots
2760
2886
  }
2761
2887
  );
2888
+ const traceView = deps.renderTraceView(
2889
+ { spans: tc.story.otelSpans },
2890
+ { escapeHtml: deps.escapeHtml }
2891
+ );
2762
2892
  const collapsedClass = deps.startCollapsed ? " collapsed" : "";
2763
2893
  const ariaExpanded = !deps.startCollapsed;
2764
2894
  return `
@@ -2778,6 +2908,193 @@ function renderScenario(args, deps) {
2778
2908
  ${steps}
2779
2909
  ${error}
2780
2910
  ${attachments}
2911
+ ${traceView}
2912
+ </div>
2913
+ </div>`;
2914
+ }
2915
+
2916
+ // src/formatters/html/renderers/trace-view.ts
2917
+ var VALID_STATUSES = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
2918
+ var TOOLTIP_MAX_LENGTH = 4096;
2919
+ function safeStatus(status) {
2920
+ return VALID_STATUSES.has(status) ? status : "unset";
2921
+ }
2922
+ function formatDuration(ms) {
2923
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
2924
+ return `${ms.toFixed(1)}ms`;
2925
+ }
2926
+ function clamp(value, min, max) {
2927
+ return Math.min(max, Math.max(min, value));
2928
+ }
2929
+ function normalizeSpans(spans) {
2930
+ const result = [];
2931
+ for (const span of spans) {
2932
+ if (!span || typeof span !== "object") continue;
2933
+ if (typeof span.spanId !== "string" || typeof span.name !== "string") continue;
2934
+ let startTimeMs;
2935
+ let durationMs;
2936
+ if (span.startTimeMs != null && span.durationMs != null) {
2937
+ startTimeMs = span.startTimeMs;
2938
+ durationMs = span.durationMs;
2939
+ } else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
2940
+ startTimeMs = span.startTimeUnixNano / 1e6;
2941
+ durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
2942
+ } else {
2943
+ continue;
2944
+ }
2945
+ durationMs = Math.max(0, durationMs);
2946
+ if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
2947
+ result.push({
2948
+ spanId: span.spanId,
2949
+ parentSpanId: span.parentSpanId,
2950
+ name: span.name,
2951
+ startTimeMs,
2952
+ durationMs,
2953
+ status: safeStatus(span.status),
2954
+ statusMessage: span.statusMessage,
2955
+ attributes: span.attributes
2956
+ });
2957
+ }
2958
+ return result;
2959
+ }
2960
+ function buildTree(spans) {
2961
+ const byId = /* @__PURE__ */ new Map();
2962
+ for (const span of spans) {
2963
+ let key = span.spanId;
2964
+ if (byId.has(key)) {
2965
+ let suffix = 2;
2966
+ while (byId.has(`${span.spanId}__dup${suffix}`)) suffix++;
2967
+ key = `${span.spanId}__dup${suffix}`;
2968
+ }
2969
+ byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
2970
+ }
2971
+ const roots = [];
2972
+ for (const node of byId.values()) {
2973
+ const parentId = node.span.parentSpanId;
2974
+ const parent = parentId ? byId.get(parentId) : void 0;
2975
+ if (parent && parent !== node) {
2976
+ parent.children.push(node);
2977
+ } else {
2978
+ roots.push(node);
2979
+ }
2980
+ }
2981
+ for (const node of byId.values()) {
2982
+ node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
2983
+ }
2984
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
2985
+ const visited = /* @__PURE__ */ new Set();
2986
+ function assignDepth(node, depth) {
2987
+ if (visited.has(node.span.spanId)) return;
2988
+ visited.add(node.span.spanId);
2989
+ node.depth = depth;
2990
+ for (const child of node.children) {
2991
+ assignDepth(child, depth + 1);
2992
+ }
2993
+ }
2994
+ for (const root of roots) {
2995
+ assignDepth(root, 0);
2996
+ }
2997
+ for (const node of byId.values()) {
2998
+ if (!visited.has(node.span.spanId)) {
2999
+ node.children = [];
3000
+ roots.push(node);
3001
+ assignDepth(node, 0);
3002
+ }
3003
+ }
3004
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
3005
+ return roots;
3006
+ }
3007
+ function flattenTree(roots) {
3008
+ const result = [];
3009
+ function walk(node) {
3010
+ result.push(node);
3011
+ for (const child of node.children) {
3012
+ walk(child);
3013
+ }
3014
+ }
3015
+ for (const root of roots) {
3016
+ walk(root);
3017
+ }
3018
+ return result;
3019
+ }
3020
+ function buildTooltip(span, escapeHtml2) {
3021
+ const parts = [];
3022
+ parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
3023
+ if (span.statusMessage) {
3024
+ parts.push(`Status: ${span.statusMessage}`);
3025
+ }
3026
+ if (span.attributes) {
3027
+ const keys = Object.keys(span.attributes).sort();
3028
+ for (const key of keys) {
3029
+ const val = span.attributes[key];
3030
+ const formatted = Array.isArray(val) ? `[${val.map((v) => String(v)).join(", ")}]` : String(val);
3031
+ parts.push(`${key}=${formatted}`);
3032
+ }
3033
+ }
3034
+ let text = parts.join("\n");
3035
+ if (text.length > TOOLTIP_MAX_LENGTH) {
3036
+ text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
3037
+ }
3038
+ return escapeHtml2(text);
3039
+ }
3040
+ function renderTraceView(args, deps) {
3041
+ if (!args.spans || args.spans.length === 0) return "";
3042
+ const normalized = normalizeSpans(args.spans);
3043
+ if (normalized.length === 0) return "";
3044
+ const roots = buildTree(normalized);
3045
+ const flat = flattenTree(roots);
3046
+ let minStart = Infinity;
3047
+ let maxEnd = -Infinity;
3048
+ for (const node of flat) {
3049
+ const s = node.span.startTimeMs;
3050
+ const e = s + node.span.durationMs;
3051
+ if (s < minStart) minStart = s;
3052
+ if (e > maxEnd) maxEnd = e;
3053
+ }
3054
+ let totalDuration = maxEnd - minStart;
3055
+ if (totalDuration <= 0) totalDuration = 1;
3056
+ const rows = flat.map((node) => {
3057
+ const { span, depth } = node;
3058
+ const indent = depth * 16;
3059
+ const minWidth = 0.5;
3060
+ let spanLeft = clamp(
3061
+ (span.startTimeMs - minStart) / totalDuration * 100,
3062
+ 0,
3063
+ 100
3064
+ );
3065
+ if (spanLeft + minWidth > 100) {
3066
+ spanLeft = 100 - minWidth;
3067
+ }
3068
+ const spanWidth = clamp(
3069
+ span.durationMs / totalDuration * 100,
3070
+ minWidth,
3071
+ 100 - spanLeft
3072
+ );
3073
+ const tooltip = buildTooltip(span, deps.escapeHtml);
3074
+ const durationLabel = formatDuration(span.durationMs);
3075
+ return ` <div class="trace-view-row">
3076
+ <div class="trace-view-name" style="padding-left: ${indent}px" title="${deps.escapeHtml(span.name)}">
3077
+ <span class="trace-view-status-dot trace-view-status-${span.status}"></span>
3078
+ ${deps.escapeHtml(span.name)}
3079
+ </div>
3080
+ <div class="trace-view-bar-container">
3081
+ <div class="trace-view-bar trace-view-bar-${span.status}" style="left: ${spanLeft.toFixed(2)}%; width: ${spanWidth.toFixed(2)}%" title="${tooltip}">${durationLabel}</div>
3082
+ </div>
3083
+ </div>`;
3084
+ }).join("\n");
3085
+ const axisEnd = formatDuration(maxEnd - minStart);
3086
+ return `<div class="trace-view collapsed">
3087
+ <div class="trace-view-header" role="button" tabindex="0" aria-expanded="false">
3088
+ <span>Spans</span>
3089
+ <span class="trace-view-count">${flat.length}</span>
3090
+ <span class="chevron">&#9660;</span>
3091
+ </div>
3092
+ <div class="trace-view-content">
3093
+ <div class="trace-view-axis">
3094
+ <span>0ms</span>
3095
+ <span>${axisEnd}</span>
3096
+ </div>
3097
+ ${rows}
2781
3098
  </div>
2782
3099
  </div>`;
2783
3100
  }
@@ -2914,6 +3231,7 @@ function createHtmlFormatter(options = {}) {
2914
3231
  renderDocs,
2915
3232
  renderErrorBox: (args, d) => renderErrorBox(args, d),
2916
3233
  renderAttachments: (args, d) => renderAttachments(args, d),
3234
+ renderTraceView: (args, d) => renderTraceView(args, d),
2917
3235
  embedScreenshots: opts.embedScreenshots
2918
3236
  };
2919
3237
  const featureDeps = {
@@ -5279,7 +5597,7 @@ function readBranchName(cwd = process.cwd()) {
5279
5597
  }
5280
5598
 
5281
5599
  // src/utils/duration.ts
5282
- function formatDuration(ms) {
5600
+ function formatDuration2(ms) {
5283
5601
  if (ms < 1e3) {
5284
5602
  return `${Math.round(ms)} ms`;
5285
5603
  }
@@ -5769,7 +6087,7 @@ export {
5769
6087
  deriveStepResults,
5770
6088
  detectCI4 as detectCI,
5771
6089
  findGitDir,
5772
- formatDuration,
6090
+ formatDuration2 as formatDuration,
5773
6091
  generateRunId,
5774
6092
  generateTestCaseId,
5775
6093
  mergeStepResults,